diff options
940 files changed, 22470 insertions, 7453 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 0ccdf37f0c2c..ab5d503eac62 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -1043,20 +1043,12 @@ aconfig_declarations { name: "device_policy_aconfig_flags", package: "android.app.admin.flags", container: "system", - exportable: true, srcs: [ "core/java/android/app/admin/flags/flags.aconfig", ], } java_aconfig_library { - name: "device_policy_exported_aconfig_flags_lib", - aconfig_declarations: "device_policy_aconfig_flags", - defaults: ["framework-minus-apex-aconfig-java-defaults"], - mode: "exported", -} - -java_aconfig_library { name: "device_policy_aconfig_flags_lib", aconfig_declarations: "device_policy_aconfig_flags", defaults: ["framework-minus-apex-aconfig-java-defaults"], diff --git a/CleanSpec.mk b/CleanSpec.mk index 02e8eecbb721..e6801034cd97 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -262,6 +262,7 @@ $(call add-clean-step, rm -rf $(SOONG_OUT_DIR)/.intermediates/frameworks/base/li $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/InProcessTethering) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/OsuLogin) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system_other/system/app/OsuLogin) +$(call add-clean-step, rm -rf $(OUT_DIR)/host/linux-x86/testcases/ravenwood-runtime) # ****************************************************************** # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER # ****************************************************************** diff --git a/Ravenwood.bp b/Ravenwood.bp index 255ec924abee..74b34fbcf2a1 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -246,12 +246,20 @@ java_genrule { visibility: ["//visibility:private"], } +java_genrule { + name: "z00-all-updatable-modules-system-stubs", + cmd: "cp $(in) $(out)", + srcs: [":all-updatable-modules-system-stubs"], + out: ["z00-all-updatable-modules-system-stubs.jar"], + visibility: ["//visibility:private"], +} + android_ravenwood_libgroup { name: "ravenwood-runtime", libs: [ "100-framework-minus-apex.ravenwood", "200-kxml2-android", - "all-updatable-modules-system-stubs", + "android.test.mock.ravenwood", "ravenwood-helper-runtime", "hoststubgen-helper-runtime.ravenwood", @@ -267,6 +275,9 @@ android_ravenwood_libgroup { "ravenwood-junit-impl-flag", "mockito-ravenwood-prebuilt", "inline-mockito-ravenwood-prebuilt", + + // It's a stub, so it should be towards the end. + "z00-all-updatable-modules-system-stubs", ], jni_libs: [ "libandroid_runtime", diff --git a/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTestGen.py b/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTestGen.py index eea3b84a4498..373355a1bbf1 100755 --- a/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTestGen.py +++ b/apct-tests/perftests/core/src/android/libcore/ImtConflictPerfTestGen.py @@ -61,8 +61,8 @@ print("package android.libcore;") imports = """ import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; -import android.test.suitebuilder.annotation.LargeTest; +import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Before; @@ -118,4 +118,4 @@ for i in range(max_conflict_depth): print(" default void f{}() {{}}".format(i*imt_size + j)) print(" }") -print("}")
\ No newline at end of file +print("}") diff --git a/apct-tests/perftests/core/src/android/libcore/varhandles/generate_java.py b/apct-tests/perftests/core/src/android/libcore/varhandles/generate_java.py index f3a1fff52205..01abdb6c4be1 100755 --- a/apct-tests/perftests/core/src/android/libcore/varhandles/generate_java.py +++ b/apct-tests/perftests/core/src/android/libcore/varhandles/generate_java.py @@ -160,8 +160,8 @@ package android.libcore.varhandles; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; -import android.test.suitebuilder.annotation.LargeTest; +import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; import org.junit.After; diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java index a4a2e80c195a..9b0f5c9636e5 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java @@ -133,7 +133,7 @@ class BlobStoreManagerShellCommand extends ShellCommand { pw.println(" --tag: Tag of the blob to delete."); pw.println("idle-maintenance"); pw.println(" Run idle maintenance which takes care of removing stale data."); - pw.println("query-blob-existence [-b BLOB_ID]"); + pw.println("query-blob-existence [-b BLOB_ID] [-u | --user USER_ID]"); pw.println(" Prints 1 if blob exists, otherwise 0."); pw.println(); } diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java index d59d430e0b78..ad54cd397413 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java @@ -491,8 +491,10 @@ public abstract class JobScheduler { * Returns a list of all currently-executing jobs. * @hide */ - @SuppressWarnings("HiddenAbstractMethod") - public abstract List<JobInfo> getStartedJobs(); + @Nullable + public List<JobInfo> getStartedJobs() { + return null; + } /** * <b>For internal system callers only!</b> @@ -501,8 +503,10 @@ public abstract class JobScheduler { * <p class="note">This is a slow operation, so it should be called sparingly. * @hide */ - @SuppressWarnings("HiddenAbstractMethod") - public abstract List<JobSnapshot> getAllJobSnapshots(); + @Nullable + public List<JobSnapshot> getAllJobSnapshots() { + return null; + } /** * @hide @@ -510,8 +514,8 @@ public abstract class JobScheduler { @RequiresPermission(allOf = { android.Manifest.permission.MANAGE_ACTIVITY_TASKS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) - @SuppressWarnings("HiddenAbstractMethod") - public abstract void registerUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer); + public void registerUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) { + } /** * @hide @@ -519,9 +523,10 @@ public abstract class JobScheduler { @RequiresPermission(allOf = { android.Manifest.permission.MANAGE_ACTIVITY_TASKS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) - @SuppressWarnings("HiddenAbstractMethod") - public abstract void unregisterUserVisibleJobObserver( - @NonNull IUserVisibleJobObserver observer); + public void unregisterUserVisibleJobObserver( + @NonNull IUserVisibleJobObserver observer) { + + } /** * @hide @@ -529,7 +534,7 @@ public abstract class JobScheduler { @RequiresPermission(allOf = { android.Manifest.permission.MANAGE_ACTIVITY_TASKS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) - @SuppressWarnings("HiddenAbstractMethod") - public abstract void notePendingUserRequestedAppStop(@NonNull String packageName, int userId, - @Nullable String debugReason); + public void notePendingUserRequestedAppStop(@NonNull String packageName, int userId, + @Nullable String debugReason) { + } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java index 852b00b38347..d5a58d11ac01 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java @@ -1771,7 +1771,13 @@ public final class FlexibilityController extends StateController { final int logicalIndex = mapping.getLogicalSlotIndex(); if (mCarrierPrivilegedCallbacks.contains(logicalIndex)) { // Callback already exists. No need to create a new one or remove it. - callbacksToRemove.remove(logicalIndex); + for (int i = callbacksToRemove.size() - 1; i >= 0; i--) { + if (callbacksToRemove.get(i) == logicalIndex) { + callbacksToRemove.remove(i); + break; + } + } + continue; } final LogicalIndexCarrierPrivilegesCallback callback = diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 77b74e9898b8..5adcd930e341 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -707,11 +707,11 @@ void BootAnimation::resizeSurface(int newWidth, int newHeight) { eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroySurface(mDisplay, mSurface); - mFlingerSurfaceControl->updateDefaultBufferSize(newWidth, newHeight); const auto limitedSize = limitSurfaceSize(newWidth, newHeight); mWidth = limitedSize.width; mHeight = limitedSize.height; + mFlingerSurfaceControl->updateDefaultBufferSize(mWidth, mHeight); EGLConfig config = getEglConfig(mDisplay); EGLSurface surface = eglCreateWindowSurface(mDisplay, config, mFlingerSurface.get(), nullptr); if (eglMakeCurrent(mDisplay, surface, surface, mContext) == EGL_FALSE) { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index eabe1f1c271a..96315ebccc49 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1143,16 +1143,13 @@ package android.app { field public static final int NAV_BAR_MODE_KIDS = 1; // 0x1 } - public static final class StatusBarManager.DisableInfo implements android.os.Parcelable { + public static final class StatusBarManager.DisableInfo { method public boolean areAllComponentsEnabled(); - method public int describeContents(); method public boolean isNavigateToHomeDisabled(); method public boolean isNotificationPeekingDisabled(); method public boolean isRecentsDisabled(); method public boolean isSearchDisabled(); method public boolean isStatusBarExpansionDisabled(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.app.StatusBarManager.DisableInfo> CREATOR; } public final class SystemServiceRegistry { @@ -10342,7 +10339,7 @@ package android.net.wifi.sharedconnectivity.app { method public int getDeviceType(); method @NonNull public android.os.Bundle getExtras(); method @NonNull public String getModelName(); - method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") public boolean isBatteryCharging(); + method @FlaggedApi("android.net.wifi.flags.network_provider_battery_charging_status") public boolean isBatteryCharging(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.NetworkProviderInfo> CREATOR; field public static final int DEVICE_TYPE_AUTO = 5; // 0x5 @@ -10356,7 +10353,7 @@ package android.net.wifi.sharedconnectivity.app { public static final class NetworkProviderInfo.Builder { ctor public NetworkProviderInfo.Builder(@NonNull String, @NonNull String); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo build(); - method @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean); + method @FlaggedApi("android.net.wifi.flags.network_provider_battery_charging_status") @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryCharging(boolean); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=4) int); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String); diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt index 62fc67b8dedf..78577e2b4090 100644 --- a/core/api/system-lint-baseline.txt +++ b/core/api/system-lint-baseline.txt @@ -1939,10 +1939,6 @@ UnflaggedApi: android.app.ActivityManager#getExternalHistoricalProcessStartReaso New API must be flagged with @FlaggedApi: method android.app.ActivityManager.getExternalHistoricalProcessStartReasons(String,int) UnflaggedApi: android.app.AppOpsManager#OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO: New API must be flagged with @FlaggedApi: field android.app.AppOpsManager.OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO -UnflaggedApi: android.app.StatusBarManager.DisableInfo#CREATOR: - New API must be flagged with @FlaggedApi: field android.app.StatusBarManager.DisableInfo.CREATOR -UnflaggedApi: android.app.StatusBarManager.DisableInfo#isBackDisabled(): - New API must be flagged with @FlaggedApi: method android.app.StatusBarManager.DisableInfo.isBackDisabled() UnflaggedApi: android.companion.virtual.VirtualDeviceManager.VirtualDevice#getPersistentDeviceId(): New API must be flagged with @FlaggedApi: method android.companion.virtual.VirtualDeviceManager.VirtualDevice.getPersistentDeviceId() UnflaggedApi: android.content.Context#THREAD_NETWORK_SERVICE: diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 2437be836086..624227dc26f3 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -463,7 +463,7 @@ package android.app { method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void togglePanel(); } - public static final class StatusBarManager.DisableInfo implements android.os.Parcelable { + public static final class StatusBarManager.DisableInfo { method public boolean isRotationSuggestionDisabled(); } @@ -966,7 +966,6 @@ package android.content { ctor public AttributionSource(int, @Nullable String, @Nullable String); ctor public AttributionSource(int, @Nullable String, @Nullable String, @NonNull android.os.IBinder); ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable java.util.Set<java.lang.String>, @Nullable android.content.AttributionSource); - ctor @FlaggedApi("android.permission.flags.attribution_source_constructor") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], @Nullable android.content.AttributionSource); ctor @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource); method public void enforceCallingPid(); } @@ -1775,16 +1774,16 @@ package android.hardware.input { } public final class InputManager { - method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void addUniqueIdAssociation(@NonNull String, @NonNull String); method @FlaggedApi("com.android.input.flags.device_associations") @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void addUniqueIdAssociationByDescriptor(@NonNull String, @NonNull String); + method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void addUniqueIdAssociationByPort(@NonNull String, @NonNull String); method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void clearAllModifierKeyRemappings(); method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptors(); method @NonNull public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String); method @NonNull @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public java.util.Map<java.lang.Integer,java.lang.Integer> getModifierKeyRemapping(); method public int getMousePointerSpeed(); method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void remapModifierKey(int, int); - method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociation(@NonNull String); method @FlaggedApi("com.android.input.flags.device_associations") @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociationByDescriptor(@NonNull String); + method @RequiresPermission("android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY") public void removeUniqueIdAssociationByPort(@NonNull String); field public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; // 0x96aec7eL } diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt index c4fe06170414..6cc71e5450ae 100644 --- a/core/api/test-lint-baseline.txt +++ b/core/api/test-lint-baseline.txt @@ -975,14 +975,14 @@ RequiresPermission: android.hardware.hdmi.HdmiControlManager#getHdmiCecVersion() Method 'getHdmiCecVersion' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.hardware.hdmi.HdmiControlManager#setHdmiCecVersion(int): Method 'setHdmiCecVersion' documentation mentions permissions already declared by @RequiresPermission -RequiresPermission: android.hardware.input.InputManager#addUniqueIdAssociation(String, String): - Method 'addUniqueIdAssociation' documentation mentions permissions without declaring @RequiresPermission RequiresPermission: android.hardware.input.InputManager#addUniqueIdAssociationByDescriptor(String, String): Method 'addUniqueIdAssociationByDescriptor' documentation mentions permissions already declared by @RequiresPermission -RequiresPermission: android.hardware.input.InputManager#removeUniqueIdAssociation(String): - Method 'removeUniqueIdAssociation' documentation mentions permissions without declaring @RequiresPermission +RequiresPermission: android.hardware.input.InputManager#addUniqueIdAssociationByPort(String, String): + Method 'addUniqueIdAssociationByPort' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.hardware.input.InputManager#removeUniqueIdAssociationByDescriptor(String): Method 'removeUniqueIdAssociationByDescriptor' documentation mentions permissions already declared by @RequiresPermission +RequiresPermission: android.hardware.input.InputManager#removeUniqueIdAssociationByPort(String): + Method 'removeUniqueIdAssociationByPort' documentation mentions permissions already declared by @RequiresPermission RequiresPermission: android.hardware.location.GeofenceHardware#addGeofence(int, int, android.hardware.location.GeofenceHardwareRequest, android.hardware.location.GeofenceHardwareCallback): Method 'addGeofence' documentation mentions permissions without declaring @RequiresPermission RequiresPermission: android.hardware.location.GeofenceHardware#getMonitoringTypes(): @@ -2035,6 +2035,10 @@ UnflaggedApi: android.content.pm.UserInfo#isPrivateProfile(): New API must be flagged with @FlaggedApi: method android.content.pm.UserInfo.isPrivateProfile() UnflaggedApi: android.credentials.CredentialProviderInfo#isPrimary(): New API must be flagged with @FlaggedApi: method android.credentials.CredentialProviderInfo.isPrimary() +UnflaggedApi: android.hardware.input.InputManager#addUniqueIdAssociationByPort(String, String): + New API must be flagged with @FlaggedApi: method android.hardware.input.InputManager.addUniqueIdAssociationByPort(String,String) +UnflaggedApi: android.hardware.input.InputManager#removeUniqueIdAssociationByPort(String): + New API must be flagged with @FlaggedApi: method android.hardware.input.InputManager.removeUniqueIdAssociationByPort(String) UnflaggedApi: android.media.AudioManager#enterAudioFocusFreezeForTest(java.util.List<java.lang.Integer>): New API must be flagged with @FlaggedApi: method android.media.AudioManager.enterAudioFocusFreezeForTest(java.util.List<java.lang.Integer>) UnflaggedApi: android.media.AudioManager#exitAudioFocusFreezeForTest(): diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index caaaf519eaca..d4812dd612c3 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -50,6 +50,7 @@ import android.app.RemoteServiceException.BadUserInitiatedJobNotificationExcepti import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException; import android.app.RemoteServiceException.CrashedByAdbException; import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException; +import android.app.RemoteServiceException.ForegroundServiceDidNotStopInTimeException; import android.app.RemoteServiceException.MissingRequestPasswordComplexityPermissionException; import android.app.assist.AssistContent; import android.app.assist.AssistStructure; @@ -2236,6 +2237,9 @@ public final class ActivityThread extends ClientTransactionHandler case ForegroundServiceDidNotStartInTimeException.TYPE_ID: throw generateForegroundServiceDidNotStartInTimeException(message, extras); + case ForegroundServiceDidNotStopInTimeException.TYPE_ID: + throw generateForegroundServiceDidNotStopInTimeException(message, extras); + case CannotPostForegroundServiceNotificationException.TYPE_ID: throw new CannotPostForegroundServiceNotificationException(message); @@ -2266,6 +2270,15 @@ public final class ActivityThread extends ClientTransactionHandler throw new ForegroundServiceDidNotStartInTimeException(message, inner); } + private ForegroundServiceDidNotStopInTimeException + generateForegroundServiceDidNotStopInTimeException(String message, Bundle extras) { + final String serviceClassName = + ForegroundServiceDidNotStopInTimeException.getServiceClassNameFromExtras(extras); + final Exception inner = (serviceClassName == null) ? null + : Service.getStartForegroundServiceStackTrace(serviceClassName); + throw new ForegroundServiceDidNotStopInTimeException(message, inner); + } + class H extends Handler { public static final int BIND_APPLICATION = 110; @UnsupportedAppUsage diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java index 7724c2369954..92543b1c7646 100644 --- a/core/java/android/app/AppCompatTaskInfo.java +++ b/core/java/android/app/AppCompatTaskInfo.java @@ -32,6 +32,11 @@ public class AppCompatTaskInfo implements Parcelable { public boolean topActivityEligibleForLetterboxEducation; /** + * Whether the letterbox education is enabled + */ + public boolean isLetterboxEducationEnabled; + + /** * Whether the direct top activity is in size compat mode on foreground. */ public boolean topActivityInSizeCompat; @@ -178,6 +183,7 @@ public class AppCompatTaskInfo implements Parcelable { == that.topActivityEligibleForUserAspectRatioButton && topActivityEligibleForLetterboxEducation == that.topActivityEligibleForLetterboxEducation + && isLetterboxEducationEnabled == that.isLetterboxEducationEnabled && topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition && topActivityLetterboxHorizontalPosition == that.topActivityLetterboxHorizontalPosition @@ -192,6 +198,7 @@ public class AppCompatTaskInfo implements Parcelable { * Reads the AppCompatTaskInfo from a parcel. */ void readFromParcel(Parcel source) { + isLetterboxEducationEnabled = source.readBoolean(); topActivityInSizeCompat = source.readBoolean(); topActivityEligibleForLetterboxEducation = source.readBoolean(); isLetterboxDoubleTapEnabled = source.readBoolean(); @@ -212,6 +219,7 @@ public class AppCompatTaskInfo implements Parcelable { */ @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeBoolean(isLetterboxEducationEnabled); dest.writeBoolean(topActivityInSizeCompat); dest.writeBoolean(topActivityEligibleForLetterboxEducation); dest.writeBoolean(isLetterboxDoubleTapEnabled); @@ -232,6 +240,7 @@ public class AppCompatTaskInfo implements Parcelable { return "AppCompatTaskInfo { topActivityInSizeCompat=" + topActivityInSizeCompat + " topActivityEligibleForLetterboxEducation= " + topActivityEligibleForLetterboxEducation + + "isLetterboxEducationEnabled= " + isLetterboxEducationEnabled + " isLetterboxDoubleTapEnabled= " + isLetterboxDoubleTapEnabled + " topActivityEligibleForUserAspectRatioButton= " + topActivityEligibleForUserAspectRatioButton diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 2d0f6fccb8f2..54f69099e081 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -482,7 +482,8 @@ public class AppOpsManager { UID_STATE_FOREGROUND_SERVICE, UID_STATE_FOREGROUND, UID_STATE_BACKGROUND, - UID_STATE_CACHED + UID_STATE_CACHED, + UID_STATE_NONEXISTENT }) public @interface UidState {} @@ -566,6 +567,12 @@ public class AppOpsManager { public static final int MIN_PRIORITY_UID_STATE = UID_STATE_CACHED; /** + * Special uid state: The UID is not running + * @hide + */ + public static final int UID_STATE_NONEXISTENT = Integer.MAX_VALUE; + + /** * Resolves the first unrestricted state given an app op. * @param op The op to resolve. * @return The last restricted UID state. @@ -596,6 +603,7 @@ public class AppOpsManager { UID_STATE_FOREGROUND, UID_STATE_BACKGROUND, UID_STATE_CACHED + // UID_STATE_NONEXISTENT isn't a real UID state, so it is excluded }; /** @hide */ @@ -615,6 +623,8 @@ public class AppOpsManager { return "bg"; case UID_STATE_CACHED: return "cch"; + case UID_STATE_NONEXISTENT: + return "gone"; default: return "unknown"; } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 4ac6bac3a2e5..9c806593b57d 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -114,6 +114,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ContrastColorUtil; +import com.android.internal.util.NewlineNormalizer; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -3190,7 +3191,7 @@ public class Notification implements Parcelable return charSequence; } - return charSequence.toString().replaceAll("[\r\n]+", "\n"); + return NewlineNormalizer.normalizeNewlines(charSequence.toString()); } private static CharSequence removeTextSizeSpans(CharSequence charSequence) { @@ -6594,6 +6595,11 @@ public class Notification implements Parcelable * @hide */ public RemoteViews createCompactHeadsUpContentView() { + // Don't show compact heads up for FSI notifications. + if (mN.fullScreenIntent != null) { + return createHeadsUpContentView(/* increasedHeight= */ false); + } + if (mStyle != null) { final RemoteViews styleView = mStyle.makeCompactHeadsUpContentView(); if (styleView != null) { @@ -7149,13 +7155,7 @@ public class Notification implements Parcelable // Adds any new extras provided by the user. if (mUserExtras != null) { final Bundle saveExtras = (Bundle) mUserExtras.clone(); - if (SystemProperties.getBoolean( - "persist.sysui.notification.builder_extras_override", false)) { - mN.extras.putAll(saveExtras); - } else { - saveExtras.putAll(mN.extras); - mN.extras = saveExtras; - } + mN.extras.putAll(saveExtras); } if (!Flags.sortSectionByTime()) { @@ -10351,7 +10351,7 @@ public class Notification implements Parcelable @Nullable @Override public RemoteViews makeCompactHeadsUpContentView() { - // TODO(b/336228700): Apply minimal HUN treatment for Call Style. + // Use existing heads up for call style. return makeHeadsUpContentView(false); } diff --git a/core/java/android/app/RemoteServiceException.java b/core/java/android/app/RemoteServiceException.java index c5ad1105397e..c624c43405a3 100644 --- a/core/java/android/app/RemoteServiceException.java +++ b/core/java/android/app/RemoteServiceException.java @@ -71,6 +71,33 @@ public class RemoteServiceException extends AndroidRuntimeException { } /** + * Exception used to crash an app process when it didn't stop after hitting its time limit. + * + * @hide + */ + public static class ForegroundServiceDidNotStopInTimeException extends RemoteServiceException { + /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */ + public static final int TYPE_ID = 7; + + private static final String KEY_SERVICE_CLASS_NAME = "serviceclassname"; + + public ForegroundServiceDidNotStopInTimeException(String msg, Throwable cause) { + super(msg, cause); + } + + public static Bundle createExtrasForService(@NonNull ComponentName service) { + Bundle b = new Bundle(); + b.putString(KEY_SERVICE_CLASS_NAME, service.getClassName()); + return b; + } + + @Nullable + public static String getServiceClassNameFromExtras(@Nullable Bundle extras) { + return (extras == null) ? null : extras.getString(KEY_SERVICE_CLASS_NAME); + } + } + + /** * Exception used to crash an app process when the system received a RemoteException * while posting a notification of a foreground service. * diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java index 726064e39778..aaddaa62a32f 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -1198,8 +1198,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac * Callback called when a particular foreground service type has timed out. * * <p>This callback is meant to give the app a small grace period of a few seconds to finish - * the foreground service of the associated type - if it fails to do so, the app will be - * declared an ANR. + * the foreground service of the associated type - if it fails to do so, the app will crash. * * <p>The foreground service of the associated type can be stopped within the time limit by * {@link android.app.Service#stopSelf()}, diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 301fef877d6c..14195c473c6d 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -21,7 +21,6 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; @@ -44,7 +43,6 @@ import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.IBinder; -import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -59,7 +57,6 @@ import com.android.internal.statusbar.IAddTileResultCallback; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.IUndoMediaTransferCallback; import com.android.internal.statusbar.NotificationVisibility; -import com.android.internal.util.DataClass; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -632,49 +629,38 @@ public class StatusBarManager { } /** - * @deprecated - * Disable some features in the status bar. Pass the bitwise-or of the DISABLE_* - * flags. To re-enable everything, pass {@link #DISABLE_NONE}. - * - * This method is deprecated and callers should use - * {@link #requestDisabledComponent(DisableInfo, String)} + * Disable some features in the status bar. Pass the bitwise-or of the DISABLE_* flags. + * To re-enable everything, pass {@link #DISABLE_NONE}. * * @hide */ - @Deprecated @UnsupportedAppUsage public void disable(int what) { - requestDisabledComponent(new DisableInfo(what & DISABLE_MASK, what & DISABLE2_MASK), null); + try { + final int userId = Binder.getCallingUserHandle().getIdentifier(); + final IStatusBarService svc = getService(); + if (svc != null) { + svc.disableForUser(what, mToken, mContext.getPackageName(), userId); + } + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } } /** - * @deprecated - * Disable some features in the status bar. Pass the bitwise-or of the DISABLE_2* - * flags. To re-enable everything, pass {@link #DISABLE2_NONE}. + * Disable additional status bar features. Pass the bitwise-or of the DISABLE2_* flags. + * To re-enable everything, pass {@link #DISABLE_NONE}. * - * This method is deprecated and callers should use - * {@link #requestDisabledComponent(DisableInfo, String)} + * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags. * * @hide */ - @Deprecated - @UnsupportedAppUsage - public void disable2(int what) { - requestDisabledComponent(new DisableInfo(what & DISABLE_MASK, what & DISABLE2_MASK), null); - } - - /** - * Disable some features in the status bar. Pass a DisableInfo object with the required flags. - * - * @hide - */ - @UnsupportedAppUsage - public void requestDisabledComponent(DisableInfo disableInfo, String reason) { + public void disable2(@Disable2Flags int what) { try { final int userId = Binder.getCallingUserHandle().getIdentifier(); final IStatusBarService svc = getService(); if (svc != null) { - svc.disableForUser(disableInfo, mToken, mContext.getPackageName(), userId, reason); + svc.disable2ForUser(what, mToken, mContext.getPackageName(), userId); } } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); @@ -902,9 +888,18 @@ public class StatusBarManager { @SystemApi @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean disabled) { - int flags1 = disabled ? DEFAULT_SETUP_DISABLE_FLAGS : DISABLE_NONE; - DisableInfo info = new DisableInfo(flags1, DISABLE2_NONE); - requestDisabledComponent(info, "setDisabledForSetup"); + try { + final int userId = Binder.getCallingUserHandle().getIdentifier(); + final IStatusBarService svc = getService(); + if (svc != null) { + svc.disableForUser(disabled ? DEFAULT_SETUP_DISABLE_FLAGS : DISABLE_NONE, + mToken, mContext.getPackageName(), userId); + svc.disable2ForUser(disabled ? DEFAULT_SETUP_DISABLE2_FLAGS : DISABLE2_NONE, + mToken, mContext.getPackageName(), userId); + } + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } } /** @@ -919,9 +914,16 @@ public class StatusBarManager { @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean disabled) { - int flags1 = disabled ? DEFAULT_SIM_LOCKED_DISABLED_FLAGS : DISABLE_NONE; - DisableInfo info = new DisableInfo(flags1, DISABLE2_NONE); - requestDisabledComponent(info, "setExpansionDisabledForSimNetworkLock"); + try { + final int userId = Binder.getCallingUserHandle().getIdentifier(); + final IStatusBarService svc = getService(); + if (svc != null) { + svc.disableForUser(disabled ? DEFAULT_SIM_LOCKED_DISABLED_FLAGS : DISABLE_NONE, + mToken, mContext.getPackageName(), userId); + } + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } } /** @@ -1313,75 +1315,33 @@ public class StatusBarManager { * @hide */ @SystemApi - @DataClass - public static final class DisableInfo implements Parcelable { + public static final class DisableInfo { - /** - * @hide - */ private boolean mStatusBarExpansion; - /** - * @hide - */ private boolean mNavigateHome; - /** - * @hide - */ private boolean mNotificationPeeking; - /** - * @hide - */ private boolean mRecents; - /** - * @hide - */ - private boolean mBack; - /** - * @hide - */ private boolean mSearch; - /** - * @hide - */ private boolean mSystemIcons; - /** - * @hide - */ private boolean mClock; - /** - * @hide - */ private boolean mNotificationIcons; - /** - * @hide - */ private boolean mRotationSuggestion; - /** - * @hide - */ - private boolean mNotificationTicker; /** @hide */ - @SuppressLint("UnflaggedApi") public DisableInfo(int flags1, int flags2) { mStatusBarExpansion = (flags1 & DISABLE_EXPAND) != 0; mNavigateHome = (flags1 & DISABLE_HOME) != 0; mNotificationPeeking = (flags1 & DISABLE_NOTIFICATION_ALERTS) != 0; mRecents = (flags1 & DISABLE_RECENT) != 0; - mBack = (flags1 & DISABLE_BACK) != 0; mSearch = (flags1 & DISABLE_SEARCH) != 0; mSystemIcons = (flags1 & DISABLE_SYSTEM_INFO) != 0; mClock = (flags1 & DISABLE_CLOCK) != 0; mNotificationIcons = (flags1 & DISABLE_NOTIFICATION_ICONS) != 0; - mNotificationTicker = (flags1 & DISABLE_NOTIFICATION_TICKER) != 0; mRotationSuggestion = (flags2 & DISABLE2_ROTATE_SUGGESTIONS) != 0; } /** @hide */ - @SuppressLint("UnflaggedApi") - public DisableInfo() { - setEnableAll(); - } + public DisableInfo() {} /** * @return {@code true} if expanding the notification shade is disabled @@ -1409,7 +1369,7 @@ public class StatusBarManager { } /** * @hide */ - public void setNavigationHomeDisabled(boolean disabled) { + public void setNagivationHomeDisabled(boolean disabled) { mNavigateHome = disabled; } @@ -1444,20 +1404,6 @@ public class StatusBarManager { } /** - * @return {@code true} if mBack is disabled - * - * @hide - */ - public boolean isBackDisabled() { - return mBack; - } - - /** @hide */ - public void setBackDisabled(boolean disabled) { - mBack = disabled; - } - - /** * @return {@code true} if mSearch is disabled * * @hide @@ -1515,20 +1461,6 @@ public class StatusBarManager { } /** - * @return {@code true} if notification ticker is disabled - * - * @hide - */ - public boolean isNotificationTickerDisabled() { - return mNotificationTicker; - } - - /** * @hide */ - public void setNotificationTickerDisabled(boolean disabled) { - mNotificationTicker = disabled; - } - - /** * Returns whether the rotation suggestion is disabled. * * @hide @@ -1538,11 +1470,6 @@ public class StatusBarManager { return mRotationSuggestion; } - /** * @hide */ - public void setRotationSuggestionDisabled(boolean disabled) { - mNotificationIcons = disabled; - } - /** * @return {@code true} if no components are disabled (default state) * @hide @@ -1550,8 +1477,8 @@ public class StatusBarManager { @SystemApi public boolean areAllComponentsEnabled() { return !mStatusBarExpansion && !mNavigateHome && !mNotificationPeeking && !mRecents - && !mBack && !mSearch && !mSystemIcons && !mClock && !mNotificationIcons - && !mNotificationTicker && !mRotationSuggestion; + && !mSearch && !mSystemIcons && !mClock && !mNotificationIcons + && !mRotationSuggestion; } /** @hide */ @@ -1560,12 +1487,10 @@ public class StatusBarManager { mNavigateHome = false; mNotificationPeeking = false; mRecents = false; - mBack = false; mSearch = false; mSystemIcons = false; mClock = false; mNotificationIcons = false; - mNotificationTicker = false; mRotationSuggestion = false; } @@ -1575,9 +1500,9 @@ public class StatusBarManager { * @hide */ public boolean areAllComponentsDisabled() { - return mStatusBarExpansion && mNavigateHome && mNotificationPeeking && mRecents && mBack - && mSearch && mSystemIcons && mClock && mNotificationIcons - && mNotificationTicker && mRotationSuggestion; + return mStatusBarExpansion && mNavigateHome && mNotificationPeeking + && mRecents && mSearch && mSystemIcons && mClock && mNotificationIcons + && mRotationSuggestion; } /** @hide */ @@ -1586,12 +1511,10 @@ public class StatusBarManager { mNavigateHome = true; mNotificationPeeking = true; mRecents = true; - mBack = true; mSearch = true; mSystemIcons = true; mClock = true; mNotificationIcons = true; - mNotificationTicker = true; mRotationSuggestion = true; } @@ -1599,19 +1522,16 @@ public class StatusBarManager { @Override public String toString() { StringBuilder sb = new StringBuilder(); - - sb.append("Disable Info: "); + sb.append("DisableInfo: "); sb.append(" mStatusBarExpansion=").append(mStatusBarExpansion ? "disabled" : "enabled"); sb.append(" mNavigateHome=").append(mNavigateHome ? "disabled" : "enabled"); sb.append(" mNotificationPeeking=") .append(mNotificationPeeking ? "disabled" : "enabled"); sb.append(" mRecents=").append(mRecents ? "disabled" : "enabled"); - sb.append(" mBack=").append(mBack ? "disabled" : "enabled"); sb.append(" mSearch=").append(mSearch ? "disabled" : "enabled"); sb.append(" mSystemIcons=").append(mSystemIcons ? "disabled" : "enabled"); sb.append(" mClock=").append(mClock ? "disabled" : "enabled"); sb.append(" mNotificationIcons=").append(mNotificationIcons ? "disabled" : "enabled"); - sb.append(" mNotificationTicker=").append(mNotificationTicker ? "disabled" : "enabled"); sb.append(" mRotationSuggestion=").append(mRotationSuggestion ? "disabled" : "enabled"); return sb.toString(); @@ -1619,7 +1539,7 @@ public class StatusBarManager { } /** - * Convert a DisableInfo to equivalent flags. + * Convert a DisableInfo to equivalent flags * @return a pair of equivalent disable flags * * @hide @@ -1632,278 +1552,14 @@ public class StatusBarManager { if (mNavigateHome) disable1 |= DISABLE_HOME; if (mNotificationPeeking) disable1 |= DISABLE_NOTIFICATION_ALERTS; if (mRecents) disable1 |= DISABLE_RECENT; - if (mBack) disable1 |= DISABLE_BACK; if (mSearch) disable1 |= DISABLE_SEARCH; if (mSystemIcons) disable1 |= DISABLE_SYSTEM_INFO; if (mClock) disable1 |= DISABLE_CLOCK; if (mNotificationIcons) disable1 |= DISABLE_NOTIFICATION_ICONS; - if (mNotificationTicker) disable1 |= DISABLE_NOTIFICATION_TICKER; if (mRotationSuggestion) disable2 |= DISABLE2_ROTATE_SUGGESTIONS; return new Pair<Integer, Integer>(disable1, disable2); } - - - - // Code below generated by codegen v1.0.23. - // - // DO NOT MODIFY! - // CHECKSTYLE:OFF Generated code - // - // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/StatusBarManager.java - // - // To exclude the generated code from IntelliJ auto-formatting enable (one-time): - // Settings > Editor > Code Style > Formatter Control - //@formatter:off - - - /** - * Creates a new DisableInfo. - * @hide - */ - @DataClass.Generated.Member - public DisableInfo( - boolean statusBarExpansion, - boolean navigateHome, - boolean notificationPeeking, - boolean recents, - boolean back, - boolean search, - boolean systemIcons, - boolean clock, - boolean notificationIcons, - boolean rotationSuggestion, - boolean notificationTicker) { - this.mStatusBarExpansion = statusBarExpansion; - this.mNavigateHome = navigateHome; - this.mNotificationPeeking = notificationPeeking; - this.mRecents = recents; - this.mBack = back; - this.mSearch = search; - this.mSystemIcons = systemIcons; - this.mClock = clock; - this.mNotificationIcons = notificationIcons; - this.mRotationSuggestion = rotationSuggestion; - this.mNotificationTicker = notificationTicker; - - // onConstructed(); // You can define this method to get a callback - } - - /** - * @hide - */ - @DataClass.Generated.Member - public boolean isStatusBarExpansion() { - return mStatusBarExpansion; - } - - /** - * @hide - */ - @DataClass.Generated.Member - public boolean isNavigateHome() { - return mNavigateHome; - } - - /** - * @hide - */ - @DataClass.Generated.Member - public boolean isNotificationPeeking() { - return mNotificationPeeking; - } - - /** - * @hide - */ - @DataClass.Generated.Member - public boolean isRecents() { - return mRecents; - } - - /** - * @hide - */ - @DataClass.Generated.Member - public boolean isBack() { - return mBack; - } - - /** - * @hide - */ - @DataClass.Generated.Member - public boolean isSearch() { - return mSearch; - } - - /** - * @hide - */ - @DataClass.Generated.Member - public boolean isSystemIcons() { - return mSystemIcons; - } - - /** - * @hide - */ - @DataClass.Generated.Member - public boolean isClock() { - return mClock; - } - - /** - * @hide - */ - @DataClass.Generated.Member - public boolean isNotificationIcons() { - return mNotificationIcons; - } - - /** - * @hide - */ - @DataClass.Generated.Member - public boolean isRotationSuggestion() { - return mRotationSuggestion; - } - - /** - * @hide - */ - @DataClass.Generated.Member - public boolean isNotificationTicker() { - return mNotificationTicker; - } - - /** - * @hide - */ - @SuppressLint("UnflaggedApi") - @Override - @DataClass.Generated.Member - public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { - // You can override field parcelling by defining methods like: - // void parcelFieldName(Parcel dest, int flags) { ... } - - int flg = 0; - if (mStatusBarExpansion) flg |= 0x1; - if (mNavigateHome) flg |= 0x2; - if (mNotificationPeeking) flg |= 0x4; - if (mRecents) flg |= 0x8; - if (mBack) flg |= 0x10; - if (mSearch) flg |= 0x20; - if (mSystemIcons) flg |= 0x40; - if (mClock) flg |= 0x80; - if (mNotificationIcons) flg |= 0x100; - if (mRotationSuggestion) flg |= 0x200; - if (mNotificationTicker) flg |= 0x400; - dest.writeInt(flg); - } - - /** - * @hide - */ - @SuppressLint("UnflaggedApi") - @Override - @DataClass.Generated.Member - public int describeContents() { return 0; } - - /** @hide */ - @SuppressWarnings({"unchecked", "RedundantCast"}) - @DataClass.Generated.Member - /* package-private */ DisableInfo(@NonNull android.os.Parcel in) { - // You can override field unparcelling by defining methods like: - // static FieldType unparcelFieldName(Parcel in) { ... } - - int flg = in.readInt(); - boolean statusBarExpansion = (flg & 0x1) != 0; - boolean navigateHome = (flg & 0x2) != 0; - boolean notificationPeeking = (flg & 0x4) != 0; - boolean recents = (flg & 0x8) != 0; - boolean back = (flg & 0x10) != 0; - boolean search = (flg & 0x20) != 0; - boolean systemIcons = (flg & 0x40) != 0; - boolean clock = (flg & 0x80) != 0; - boolean notificationIcons = (flg & 0x100) != 0; - boolean rotationSuggestion = (flg & 0x200) != 0; - boolean notificationTicker = (flg & 0x400) != 0; - - this.mStatusBarExpansion = statusBarExpansion; - this.mNavigateHome = navigateHome; - this.mNotificationPeeking = notificationPeeking; - this.mRecents = recents; - this.mBack = back; - this.mSearch = search; - this.mSystemIcons = systemIcons; - this.mClock = clock; - this.mNotificationIcons = notificationIcons; - this.mRotationSuggestion = rotationSuggestion; - this.mNotificationTicker = notificationTicker; - - // onConstructed(); // You can define this method to get a callback - } - - @DataClass.Generated.Member - public static final @NonNull Parcelable.Creator<DisableInfo> CREATOR - = new Parcelable.Creator<DisableInfo>() { - @Override - public DisableInfo[] newArray(int size) { - return new DisableInfo[size]; - } - - @Override - public DisableInfo createFromParcel(@NonNull android.os.Parcel in) { - return new DisableInfo(in); - } - }; - - @DataClass.Generated( - time = 1708625947132L, - codegenVersion = "1.0.23", - sourceFile = "frameworks/base/core/java/android/app/StatusBarManager.java", - inputSignatures = "private boolean mStatusBarExpansion\nprivate boolean " - + "mNavigateHome\nprivate boolean mNotificationPeeking\nprivate " - + "boolean mRecents\nprivate boolean mBack\nprivate boolean mSearch\n" - + "private boolean mSystemIcons\nprivate boolean mClock\nprivate " - + "boolean mNotificationIcons\nprivate boolean mRotationSuggestion\n" - + "private boolean mNotificationTicker\npublic " - + "@android.annotation.SystemApi boolean isStatusBarExpansionDisabled()\n" - + "public void setStatusBarExpansionDisabled(boolean)\npublic " - + "@android.annotation.SystemApi boolean isNavigateToHomeDisabled()\npublic" - + " void setNavigationHomeDisabled(boolean)\npublic " - + "@android.annotation.SystemApi boolean isNotificationPeekingDisabled()" - + "\npublic void setNotificationPeekingDisabled(boolean)\npublic " - + "@android.annotation.SystemApi boolean isRecentsDisabled()\npublic " - + "void setRecentsDisabled(boolean)\npublic boolean isBackDisabled()" - + "\npublic void setBackDisabled(boolean)\npublic " - + "@android.annotation.SystemApi boolean isSearchDisabled()\npublic " - + "void setSearchDisabled(boolean)\npublic boolean " - + "areSystemIconsDisabled()\npublic void setSystemIconsDisabled(boolean)\n" - + "public boolean isClockDisabled()\npublic " - + "void setClockDisabled(boolean)\npublic boolean " - + "areNotificationIconsDisabled()\npublic void " - + "setNotificationIconsDisabled(boolean)\npublic boolean " - + "isNotificationTickerDisabled()\npublic void " - + "setNotificationTickerDisabled(boolean)\npublic " - + "@android.annotation.TestApi boolean isRotationSuggestionDisabled()\n" - + "public void setRotationSuggestionDisabled(boolean)\npublic " - + "@android.annotation.SystemApi boolean areAllComponentsEnabled()\npublic" - + " void setEnableAll()\npublic boolean areAllComponentsDisabled()\n" - + "public void setDisableAll()\npublic @android.annotation.NonNull " - + "@java.lang.Override java.lang.String toString()\npublic " - + "android.util.Pair<java.lang.Integer,java.lang.Integer> toFlags()\n" - + "class DisableInfo extends java.lang.Object implements " - + "[android.os.Parcelable]\n@com.android.internal.util.DataClass") - @Deprecated - private void __metadata() {} - - - //@formatter:on - // End of generated code - } /** diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index efd5a455af89..ef8501f563dc 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -351,6 +351,12 @@ public class TaskInfo { } /** @hide */ + public boolean isFreeform() { + return configuration.windowConfiguration.getWindowingMode() + == WindowConfiguration.WINDOWING_MODE_FREEFORM; + } + + /** @hide */ @WindowConfiguration.ActivityType public int getActivityType() { return configuration.windowConfiguration.getActivityType(); diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig index 8c4667f15299..bb24fd19315e 100644 --- a/core/java/android/app/activity_manager.aconfig +++ b/core/java/android/app/activity_manager.aconfig @@ -50,3 +50,23 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + namespace: "backstage_power" + name: "gate_fgs_timeout_anr_behavior" + description: "Gate the new behavior where an ANR is thrown once an FGS times out." + bug: "339315145" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + namespace: "backstage_power" + name: "enable_fgs_timeout_crash_behavior" + description: "Enable the new behavior where the app is crashed once an FGS times out." + bug: "339526947" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 83daa4524696..3d6ec19299cb 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -217,6 +217,16 @@ flag { } flag { + name: "disallow_user_control_stopped_state_fix" + namespace: "enterprise" + description: "Ensure DPM.setUserControlDisabledPackages() clears FLAG_STOPPED for the app" + bug: "330688482" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "esim_management_ux_enabled" namespace: "enterprise" description: "Enable UX changes for esim management" @@ -309,7 +319,6 @@ flag { namespace: "enterprise" description: "Fix for compatibility issue introduced from using single_user mode on pre-Android V builds" bug: "338050276" - is_exported: true metadata { purpose: PURPOSE_BUGFIX } diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig index 84588577b6d6..36d0e081af2a 100644 --- a/core/java/android/companion/flags.aconfig +++ b/core/java/android/companion/flags.aconfig @@ -39,3 +39,11 @@ flag { description: "Expose perm sync user consent API" bug: "309528663" } + +flag { + name: "ongoing_perm_sync" + is_exported: true + namespace: "companion" + description: "Enable ongoing perm sync" + bug: "338469649" +}
\ No newline at end of file diff --git a/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl b/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl index 9f09d043a89b..5a1325519699 100644 --- a/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl +++ b/core/java/android/companion/virtualnative/IVirtualDeviceManagerNative.aidl @@ -48,6 +48,8 @@ interface IVirtualDeviceManagerNative { const int POLICY_TYPE_AUDIO = 1; const int POLICY_TYPE_RECENTS = 2; const int POLICY_TYPE_ACTIVITY = 3; + const int POLICY_TYPE_CLIPBOARD = 4; + const int POLICY_TYPE_CAMERA = 5; /** * Returns the IDs for all VirtualDevices where an app with the given is running. @@ -62,4 +64,4 @@ interface IVirtualDeviceManagerNative { * Returns the device policy for the given virtual device and policy type. */ int getDevicePolicy(int deviceId, int policyType); -}
\ No newline at end of file +} diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java index b070742178e6..37f419d717c2 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -162,17 +162,6 @@ public final class AttributionSource implements Parcelable { /** @hide */ @TestApi - @FlaggedApi(Flags.FLAG_ATTRIBUTION_SOURCE_CONSTRUCTOR) - public AttributionSource(int uid, int pid, @Nullable String packageName, - @Nullable String attributionTag, @NonNull IBinder token, - @Nullable String[] renouncedPermissions, - @Nullable AttributionSource next) { - this(uid, pid, packageName, attributionTag, token, renouncedPermissions, - Context.DEVICE_ID_DEFAULT, next); - } - - /** @hide */ - @TestApi @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) public AttributionSource(int uid, int pid, @Nullable String packageName, @Nullable String attributionTag, @NonNull IBinder token, @@ -473,6 +462,20 @@ public final class AttributionSource implements Parcelable { } /** + * @return The next package's device Id from its context. + * This device ID is used for permissions checking during attribution source validation. + * + * @hide + */ + public int getNextDeviceId() { + if (mAttributionSourceState.next != null + && mAttributionSourceState.next.length > 0) { + return mAttributionSourceState.next[0].deviceId; + } + return Context.DEVICE_ID_DEFAULT; + } + + /** * Checks whether this attribution source can be trusted. That is whether * the app it refers to created it and provided to the attribution chain. * diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index be40143f5bd3..cd3ce874d68a 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -1492,12 +1492,12 @@ public final class ShortcutInfo implements Parcelable { /** * Sets which surfaces a shortcut will be excluded from. * - * If the shortcut is set to be excluded from {@link #SURFACE_LAUNCHER}, shortcuts will be - * excluded from the search result of {@link android.content.pm.LauncherApps#getShortcuts( - * android.content.pm.LauncherApps.ShortcutQuery, UserHandle)} nor - * {@link android.content.pm.ShortcutManager#getShortcuts(int)}. This generally means the - * shortcut would not be displayed by a launcher app (e.g. in Long-Press menu), while - * remain visible in other surfaces such as assistant or on-device-intelligence. + * This API is reserved for future extension. Currently, marking a shortcut to be + * excluded from {@link #SURFACE_LAUNCHER} will not publish the shortcut, thus + * the following operations will be a no-op: + * {@link android.content.pm.ShortcutManager#pushDynamicShortcut(android.content.pm.ShortcutInfo)}, + * {@link android.content.pm.ShortcutManager#addDynamicShortcuts(List)}, and + * {@link android.content.pm.ShortcutManager#setDynamicShortcuts(List)}. */ @NonNull public Builder setExcludedFromSurfaces(final int surfaces) { diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index cee8d96fb0d7..061e7f711ba5 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -254,6 +254,14 @@ flag { } flag { + name: "wait_application_killed" + namespace: "package_manager_service" + description: "Feature flag to control whether to wait until the application is killed when clear application data" + bug: "31009094" + is_fixed_read_only: true +} + +flag { name: "component_state_changed_metrics" namespace: "package_manager_service" description: "Feature flag to log the metrics when the component state is changed." diff --git a/core/java/android/credentials/GetCandidateCredentialsResponse.java b/core/java/android/credentials/GetCandidateCredentialsResponse.java index 3d8ccaaa45cc..c70eff42c732 100644 --- a/core/java/android/credentials/GetCandidateCredentialsResponse.java +++ b/core/java/android/credentials/GetCandidateCredentialsResponse.java @@ -18,6 +18,8 @@ package android.credentials; import android.annotation.Hide; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; import android.content.Intent; import android.credentials.selection.GetCredentialProviderData; import android.os.Parcel; @@ -39,6 +41,9 @@ public final class GetCandidateCredentialsResponse implements Parcelable { @NonNull private final List<GetCredentialProviderData> mCandidateProviderDataList; + @Nullable + private final ComponentName mPrimaryProviderComponentName; + @NonNull private final Intent mIntent; @@ -48,13 +53,15 @@ public final class GetCandidateCredentialsResponse implements Parcelable { @Hide public GetCandidateCredentialsResponse( @NonNull List<GetCredentialProviderData> candidateProviderDataList, - @NonNull Intent intent + @NonNull Intent intent, + @Nullable ComponentName primaryProviderComponentName ) { Preconditions.checkCollectionNotEmpty( candidateProviderDataList, /*valueName=*/ "candidateProviderDataList"); mCandidateProviderDataList = new ArrayList<>(candidateProviderDataList); mIntent = intent; + mPrimaryProviderComponentName = primaryProviderComponentName; } /** @@ -67,6 +74,16 @@ public final class GetCandidateCredentialsResponse implements Parcelable { } /** + * Returns the primary provider component name. + * + * @hide + */ + @Nullable + public ComponentName getPrimaryProviderComponentName() { + return mPrimaryProviderComponentName; + } + + /** * Returns candidate provider data list. * * @hide @@ -83,12 +100,15 @@ public final class GetCandidateCredentialsResponse implements Parcelable { AnnotationValidations.validate(NonNull.class, null, mCandidateProviderDataList); mIntent = in.readTypedObject(Intent.CREATOR); + + mPrimaryProviderComponentName = in.readTypedObject(ComponentName.CREATOR); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeTypedList(mCandidateProviderDataList); dest.writeTypedObject(mIntent, flags); + dest.writeTypedObject(mPrimaryProviderComponentName, flags); } @Override diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index a0e40f6390ee..61f1ee11e059 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -34,6 +34,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.TestApi; +import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.graphics.Bitmap; @@ -603,7 +604,6 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan mPromptInfo.setIsForLegacyFingerprintManager(sensorId); return this; } - // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java) /** * Set if emergency call button should show, for example if biometrics are @@ -613,12 +613,33 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * @hide */ @NonNull + @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL}) public Builder setShowEmergencyCallButton(boolean showEmergencyCallButton) { mPromptInfo.setShowEmergencyCallButton(showEmergencyCallButton); return this; } /** + * Set caller's component name for getting logo icon/description. This should only be used + * by ConfirmDeviceCredentialActivity, see b/337082634 for more context. + * + * @param componentNameForConfirmDeviceCredentialActivity set the component name for + * ConfirmDeviceCredentialActivity. + * @return This builder. + * @hide + */ + @NonNull + @RequiresPermission(anyOf = {TEST_BIOMETRIC, USE_BIOMETRIC_INTERNAL}) + public Builder setComponentNameForConfirmDeviceCredentialActivity( + ComponentName componentNameForConfirmDeviceCredentialActivity) { + mPromptInfo.setComponentNameForConfirmDeviceCredentialActivity( + componentNameForConfirmDeviceCredentialActivity); + return this; + } + + // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/PromptInfo.java) + + /** * Creates a {@link BiometricPrompt}. * * @return An instance of {@link BiometricPrompt}. diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java index 18b75c9b8f8f..bb07b9b881f8 100644 --- a/core/java/android/hardware/biometrics/PromptInfo.java +++ b/core/java/android/hardware/biometrics/PromptInfo.java @@ -19,6 +19,7 @@ package android.hardware.biometrics; import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.ComponentName; import android.graphics.Bitmap; import android.os.Parcel; import android.os.Parcelable; @@ -56,6 +57,7 @@ public class PromptInfo implements Parcelable { private boolean mIsForLegacyFingerprintManager = false; private boolean mShowEmergencyCallButton = false; private boolean mUseParentProfileForDeviceCredential = false; + private ComponentName mComponentNameForConfirmDeviceCredentialActivity = null; public PromptInfo() { @@ -87,6 +89,8 @@ public class PromptInfo implements Parcelable { mIsForLegacyFingerprintManager = in.readBoolean(); mShowEmergencyCallButton = in.readBoolean(); mUseParentProfileForDeviceCredential = in.readBoolean(); + mComponentNameForConfirmDeviceCredentialActivity = in.readParcelable( + ComponentName.class.getClassLoader(), ComponentName.class); } public static final Creator<PromptInfo> CREATOR = new Creator<PromptInfo>() { @@ -132,10 +136,11 @@ public class PromptInfo implements Parcelable { dest.writeBoolean(mIsForLegacyFingerprintManager); dest.writeBoolean(mShowEmergencyCallButton); dest.writeBoolean(mUseParentProfileForDeviceCredential); + dest.writeParcelable(mComponentNameForConfirmDeviceCredentialActivity, 0); } // LINT.IfChange - public boolean containsTestConfigurations() { + public boolean requiresTestOrInternalPermission() { if (mIsForLegacyFingerprintManager && mAllowedSensorIds.size() == 1 && !mAllowBackgroundAuthentication) { @@ -148,11 +153,15 @@ public class PromptInfo implements Parcelable { return true; } else if (mIgnoreEnrollmentState) { return true; + } else if (mShowEmergencyCallButton) { + return true; + } else if (mComponentNameForConfirmDeviceCredentialActivity != null) { + return true; } return false; } - public boolean containsPrivateApiConfigurations() { + public boolean requiresInternalPermission() { if (mDisallowBiometricsIfPolicyExists) { return true; } else if (mUseDefaultTitle) { @@ -177,7 +186,7 @@ public class PromptInfo implements Parcelable { * Currently, logo res, logo bitmap, logo description, PromptContentViewWithMoreOptions needs * this permission. */ - public boolean containsAdvancedApiConfigurations() { + public boolean requiresAdvancedPermission() { if (mLogoRes != -1) { return true; } else if (mLogoBitmap != null) { @@ -305,6 +314,12 @@ public class PromptInfo implements Parcelable { mShowEmergencyCallButton = showEmergencyCallButton; } + public void setComponentNameForConfirmDeviceCredentialActivity( + ComponentName componentNameForConfirmDeviceCredentialActivity) { + mComponentNameForConfirmDeviceCredentialActivity = + componentNameForConfirmDeviceCredentialActivity; + } + public void setUseParentProfileForDeviceCredential( boolean useParentProfileForDeviceCredential) { mUseParentProfileForDeviceCredential = useParentProfileForDeviceCredential; @@ -417,6 +432,10 @@ public class PromptInfo implements Parcelable { return mShowEmergencyCallButton; } + public ComponentName getComponentNameForConfirmDeviceCredentialActivity() { + return mComponentNameForConfirmDeviceCredentialActivity; + } + private void checkOnlyOneLogoSet() { if (mLogoRes != -1 && mLogoBitmap != null) { throw new IllegalStateException( diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index a01961206319..2b7d8f155bff 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -838,7 +838,10 @@ public final class CameraManager { return new CameraExtensionCharacteristics(mContext, cameraId, characteristicsMap); } - private Map<String, CameraCharacteristics> getPhysicalIdToCharsMap( + /** + * @hide + */ + public Map<String, CameraCharacteristics> getPhysicalIdToCharsMap( CameraCharacteristics chars) throws CameraAccessException { HashMap<String, CameraCharacteristics> physicalIdsToChars = new HashMap<String, CameraCharacteristics>(); @@ -974,8 +977,6 @@ public final class CameraManager { final int oomScoreOffset, int rotationOverride) throws CameraAccessException { CameraCharacteristics characteristics = getCameraCharacteristics(cameraId); CameraDevice device = null; - Map<String, CameraCharacteristics> physicalIdsToChars = - getPhysicalIdToCharsMap(characteristics); synchronized (mLock) { ICameraDeviceUser cameraUser = null; CameraDevice.CameraDeviceSetup cameraDeviceSetup = null; @@ -990,7 +991,7 @@ public final class CameraManager { callback, executor, characteristics, - physicalIdsToChars, + this, mContext.getApplicationInfo().targetSdkVersion, mContext, cameraDeviceSetup); ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks(); diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java index 6d9b51cbd003..2e1e90c78f3a 100644 --- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java @@ -200,6 +200,8 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes supportedCaptureSizes.put(format, supportedSizes); } } + + int captureFormat = ImageFormat.UNKNOWN; Surface burstCaptureSurface = CameraExtensionUtils.getBurstCaptureSurface( config.getOutputConfigurations(), supportedCaptureSizes); OutputConfiguration burstCaptureOutputConfig = null; @@ -210,6 +212,12 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes } } suitableSurfaceCount++; + + if (Flags.analytics24q3()) { + CameraExtensionUtils.SurfaceInfo burstCaptureSurfaceInfo = + CameraExtensionUtils.querySurface(burstCaptureSurface); + captureFormat = burstCaptureSurfaceInfo.mFormat; + } } if (suitableSurfaceCount != config.getOutputConfigurations().size()) { @@ -249,6 +257,9 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes burstCaptureOutputConfig, postviewOutputConfig, config.getStateCallback(), config.getExecutor(), sessionId, token, config.getExtension()); + if (Flags.analytics24q3()) { + ret.mStatsAggregator.setCaptureFormat(captureFormat); + } ret.mStatsAggregator.setClientName(ctx.getOpPackageName()); ret.mStatsAggregator.setExtensionType(config.getExtension()); diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 81bb9ac33df2..e2b409fe35f1 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -31,6 +31,7 @@ import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraExtensionCharacteristics; +import android.hardware.camera2.CameraManager; import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CameraOfflineSession; import android.hardware.camera2.CaptureFailure; @@ -146,7 +147,8 @@ public class CameraDeviceImpl extends CameraDevice private final String mCameraId; private final CameraCharacteristics mCharacteristics; - private final Map<String, CameraCharacteristics> mPhysicalIdsToChars; + private Map<String, CameraCharacteristics> mPhysicalIdsToChars; + private final CameraManager mCameraManager; private final int mTotalPartialCount; private final Context mContext; @@ -341,11 +343,12 @@ public class CameraDeviceImpl extends CameraDevice public CameraDeviceImpl(String cameraId, StateCallback callback, Executor executor, CameraCharacteristics characteristics, - Map<String, CameraCharacteristics> physicalIdsToChars, + @NonNull CameraManager manager, int appTargetSdkVersion, Context ctx, @Nullable CameraDevice.CameraDeviceSetup cameraDeviceSetup) { - if (cameraId == null || callback == null || executor == null || characteristics == null) { + if (cameraId == null || callback == null || executor == null || characteristics == null + || manager == null) { throw new IllegalArgumentException("Null argument given"); } mCameraId = cameraId; @@ -357,7 +360,7 @@ public class CameraDeviceImpl extends CameraDevice mDeviceExecutor = executor; } mCharacteristics = characteristics; - mPhysicalIdsToChars = physicalIdsToChars; + mCameraManager = manager; mAppTargetSdkVersion = appTargetSdkVersion; mContext = ctx; mCameraDeviceSetup = cameraDeviceSetup; @@ -379,6 +382,18 @@ public class CameraDeviceImpl extends CameraDevice } } + private Map<String, CameraCharacteristics> getPhysicalIdToChars() { + if (mPhysicalIdsToChars == null) { + try { + mPhysicalIdsToChars = mCameraManager.getPhysicalIdToCharsMap(mCharacteristics); + } catch (CameraAccessException e) { + Log.e(TAG, "Unable to query the physical characteristics map!"); + } + } + + return mPhysicalIdsToChars; + } + public CameraDeviceCallbacks getCallbacks() { return mCallbacks; } @@ -1598,7 +1613,7 @@ public class CameraDeviceImpl extends CameraDevice return true; } - for (Map.Entry<String, CameraCharacteristics> entry : mPhysicalIdsToChars.entrySet()) { + for (Map.Entry<String, CameraCharacteristics> entry : getPhysicalIdToChars().entrySet()) { configMap = entry.getValue().get(ck); if (configMap != null && @@ -2621,7 +2636,7 @@ public class CameraDeviceImpl extends CameraDevice public void createExtensionSession(ExtensionSessionConfiguration extensionConfiguration) throws CameraAccessException { HashMap<String, CameraCharacteristics> characteristicsMap = new HashMap<>( - mPhysicalIdsToChars); + getPhysicalIdToChars()); characteristicsMap.put(mCameraId, mCharacteristics); boolean initializationFailed = true; IBinder token = new Binder(TAG + " : " + mNextSessionId++); diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java index 3ae319999e35..a4ae398782b4 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java @@ -200,10 +200,18 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { supportedCaptureSizes.put(format, supportedSizes); } } + + int captureFormat = ImageFormat.UNKNOWN; Surface burstCaptureSurface = CameraExtensionUtils.getBurstCaptureSurface( config.getOutputConfigurations(), supportedCaptureSizes); if (burstCaptureSurface != null) { suitableSurfaceCount++; + + if (Flags.analytics24q3()) { + CameraExtensionUtils.SurfaceInfo burstCaptureSurfaceInfo = + CameraExtensionUtils.querySurface(burstCaptureSurface); + captureFormat = burstCaptureSurfaceInfo.mFormat; + } } if (suitableSurfaceCount != config.getOutputConfigurations().size()) { @@ -258,6 +266,9 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { extensionChars.getAvailableCaptureResultKeys(config.getExtension()), config.getExtension()); + if (Flags.analytics24q3()) { + session.mStatsAggregator.setCaptureFormat(captureFormat); + } session.mStatsAggregator.setClientName(ctx.getOpPackageName()); session.mStatsAggregator.setExtensionType(config.getExtension()); diff --git a/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java b/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java index 3050a51d7955..c75e4187b7bb 100644 --- a/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java +++ b/core/java/android/hardware/camera2/utils/ExtensionSessionStatsAggregator.java @@ -70,6 +70,23 @@ public class ExtensionSessionStatsAggregator { } /** + * Set the capture format. + * + * @param format Format of requested capture. + */ + public void setCaptureFormat(int format) { + synchronized (mLock) { + if (mIsDone) { + return; + } + if (DEBUG) { + Log.v(TAG, "Setting capture format: " + format); + } + mStats.captureFormat = format; + } + } + + /** * Set extension type. * * @param extensionType Type of extension. Must match one of @@ -116,7 +133,8 @@ public class ExtensionSessionStatsAggregator { + " cameraId: '" + stats.cameraId + "'\n" + " clientName: '" + stats.clientName + "'\n" + " type: '" + stats.type + "'\n" - + " isAdvanced: '" + stats.isAdvanced + "'\n"; + + " isAdvanced: '" + stats.isAdvanced + "'\n" + + " captureFormat: '" + stats.captureFormat + "'\n"; } /** diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java index ac043d31a09f..91b05c2815fa 100644 --- a/core/java/android/hardware/hdmi/HdmiControlManager.java +++ b/core/java/android/hardware/hdmi/HdmiControlManager.java @@ -1353,9 +1353,6 @@ public final class HdmiControlManager { /** * Get a snapshot of the real-time status of the devices on the CEC bus. * - * <p>This only applies to devices with switch functionality, which are devices with one - * or more than one HDMI inputs. - * * @return a list of {@link HdmiDeviceInfo} of the connected CEC devices on the CEC bus. An * empty list will be returned if there is none. */ diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index 243ae142d9b6..45b316aa5498 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -148,8 +148,6 @@ interface IInputManager { IInputDeviceBatteryState getBatteryState(int deviceId); - void setPointerIconType(int typeId); - void setCustomPointerIcon(in PointerIcon icon); boolean setPointerIcon(in PointerIcon icon, int displayId, int deviceId, int pointerId, in IBinder inputToken); @@ -173,9 +171,9 @@ interface IInputManager { void removeUniqueIdAssociationByDescriptor(in String inputDeviceDescriptor); // Add a runtime association between the input device and display, using device's port. - void addUniqueIdAssociation(in String inputPort, in String displayUniqueId); + void addUniqueIdAssociationByPort(in String inputPort, in String displayUniqueId); // Remove the runtime association between the input device and display, using device's port. - void removeUniqueIdAssociation(in String inputPort); + void removeUniqueIdAssociationByPort(in String inputPort); InputSensorInfo[] getSensorList(int deviceId); diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index dd4ea31af17d..7527aa7197e9 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -992,21 +992,14 @@ public final class InputManager { } /** - * Changes the mouse pointer's icon shape into the specified id. - * - * @param iconId The id of the pointer graphic, as a value between - * {@link PointerIcon#TYPE_ARROW} and {@link PointerIcon#TYPE_HANDWRITING}. + * This method exists for backwards-compatibility, and is a no-op. * + * @deprecated * @hide */ @UnsupportedAppUsage public void setPointerIconType(int iconId) { - mGlobal.setPointerIconType(iconId); - } - - /** @hide */ - public void setCustomPointerIcon(PointerIcon icon) { - mGlobal.setCustomPointerIcon(icon); + Log.e(TAG, "setPointerIcon: Unsupported app usage!"); } /** @hide */ @@ -1101,10 +1094,9 @@ public final class InputManager { */ @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY) @TestApi - // TODO(b/324075859): Rename to addUniqueIdAssociationByPort - public void addUniqueIdAssociation(@NonNull String inputPort, + public void addUniqueIdAssociationByPort(@NonNull String inputPort, @NonNull String displayUniqueId) { - mGlobal.addUniqueIdAssociation(inputPort, displayUniqueId); + mGlobal.addUniqueIdAssociationByPort(inputPort, displayUniqueId); } /** @@ -1117,9 +1109,8 @@ public final class InputManager { */ @RequiresPermission(android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY) @TestApi - // TODO(b/324075859): Rename to removeUniqueIdAssociationByPort - public void removeUniqueIdAssociation(@NonNull String inputPort) { - mGlobal.removeUniqueIdAssociation(inputPort); + public void removeUniqueIdAssociationByPort(@NonNull String inputPort) { + mGlobal.removeUniqueIdAssociationByPort(inputPort); } /** diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java index a9c97b1a0e51..fcd5a3ed06c0 100644 --- a/core/java/android/hardware/input/InputManagerGlobal.java +++ b/core/java/android/hardware/input/InputManagerGlobal.java @@ -1411,28 +1411,6 @@ public final class InputManagerGlobal { } /** - * @see InputManager#setPointerIconType(int) - */ - public void setPointerIconType(int iconId) { - try { - mIm.setPointerIconType(iconId); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } - } - - /** - * @see InputManager#setCustomPointerIcon(PointerIcon) - */ - public void setCustomPointerIcon(PointerIcon icon) { - try { - mIm.setCustomPointerIcon(icon); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } - } - - /** * @see InputManager#setPointerIcon(PointerIcon, int, int, int, IBinder) */ public boolean setPointerIcon(PointerIcon icon, int displayId, int deviceId, int pointerId, @@ -1467,22 +1445,23 @@ public final class InputManagerGlobal { } /** - * @see InputManager#addUniqueIdAssociation(String, String) + * @see InputManager#addUniqueIdAssociationByPort(String, String) */ - public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) { + public void addUniqueIdAssociationByPort(@NonNull String inputPort, + @NonNull String displayUniqueId) { try { - mIm.addUniqueIdAssociation(inputPort, displayUniqueId); + mIm.addUniqueIdAssociationByPort(inputPort, displayUniqueId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * @see InputManager#removeUniqueIdAssociation(String) + * @see InputManager#removeUniqueIdAssociationByPort(String) */ - public void removeUniqueIdAssociation(@NonNull String inputPort) { + public void removeUniqueIdAssociationByPort(@NonNull String inputPort) { try { - mIm.removeUniqueIdAssociation(inputPort); + mIm.removeUniqueIdAssociationByPort(inputPort); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java index 9a63394d3ca1..49ab15a40a8e 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -429,10 +429,9 @@ public class BaseBundle { "Lazy values ref count below 0"); // No more lazy values in mMap, so we can recycle the parcel early rather than // waiting for the next GC run - if (mLazyValues == 0) { - Preconditions.checkState(mWeakParcelledData.get() != null, - "Parcel recycled earlier than expected"); - recycleParcel(mWeakParcelledData.get()); + Parcel parcel = mWeakParcelledData.get(); + if (mLazyValues == 0 && parcel != null) { + recycleParcel(parcel); mWeakParcelledData = null; } } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index c6a92033b0d4..2f0d63401e33 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1930,12 +1930,10 @@ public class UserManager { public static final String DISALLOW_THREAD_NETWORK = "no_thread_network"; /** - * This user restriction specifies if the user is able to add SIMs to the device. + * This user restriction specifies if the user is able to add embedded SIMs to the device. * * <p> - * This restriction blocks the download of embedded SIMs, and disables any physical SIMs. - * If any embedded SIMs are already on the device, then they are removed. This restriction - * does not affect SIMs provisioned to the device by device owners or profile owners. + * This restriction blocks the download of embedded SIMs. * * <p> * This restriction can only be set by a device owner or a profile owner of an @@ -1951,6 +1949,7 @@ public class UserManager { * * <p>Key for user restrictions. * <p>Type: Boolean + * * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index b58830861c5e..2ca58d16eaae 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -44,14 +44,6 @@ flag { } flag { - name: "attribution_source_constructor" - is_exported: true - namespace: "permissions" - description: "enable AttributionSource(int, int, String, String, IBinder, String[], AttributionSource)" - bug: "304478648" -} - -flag { name: "enhanced_confirmation_mode_apis_enabled" is_exported: true is_fixed_read_only: true @@ -174,6 +166,16 @@ flag { } flag { + name: "finish_running_ops_for_killed_packages" + namespace: "permissions" + description: "Finish all appops for a dead app process" + bug: "234630570" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "runtime_permission_appops_mapping_enabled" is_fixed_read_only: true namespace: "permissions" diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e6ddf3556490..009713f0f942 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5123,13 +5123,6 @@ public final class Settings { public static final String SCREEN_BRIGHTNESS = "screen_brightness"; /** - * The screen backlight brightness between 0.0f and 1.0f. - * @hide - */ - @Readable - public static final String SCREEN_BRIGHTNESS_FLOAT = "screen_brightness_float"; - - /** * Control whether to enable automatic brightness mode. */ @Readable @@ -6273,7 +6266,6 @@ public final class Settings { PUBLIC_SETTINGS.add(DIM_SCREEN); PUBLIC_SETTINGS.add(SCREEN_OFF_TIMEOUT); PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS); - PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_FLOAT); PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_MODE); PUBLIC_SETTINGS.add(MODE_RINGER_STREAMS_AFFECTED); PUBLIC_SETTINGS.add(MUTE_STREAMS_AFFECTED); diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig index 51758aa32102..ee5e533364ff 100644 --- a/core/java/android/security/flags.aconfig +++ b/core/java/android/security/flags.aconfig @@ -81,8 +81,15 @@ flag { } flag { - name: "report_primary_auth_attempts" - namespace: "biometrics" - description: "Report primary auth attempts from LockSettingsService" - bug: "285053096" + name: "report_primary_auth_attempts" + namespace: "biometrics" + description: "Report primary auth attempts from LockSettingsService" + bug: "285053096" +} + +flag { + name: "dump_attestation_verifications" + namespace: "hardware_backed_security" + description: "Add a dump capability for attestation_verification service" + bug: "335498868" } diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java index 92f2c321c1db..3cd705a3c19c 100644 --- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java +++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java @@ -86,7 +86,8 @@ public final class CredentialProviderInfoFactory { @NonNull Context context, @NonNull ComponentName serviceComponent, int userId, - boolean isSystemProvider) + boolean isSystemProvider, + boolean isPrimary) throws PackageManager.NameNotFoundException { return create( context, @@ -94,7 +95,7 @@ public final class CredentialProviderInfoFactory { isSystemProvider, /* disableSystemAppVerificationForTests= */ false, /* isEnabled= */ false, - /* isPrimary= */ false); + isPrimary); } /** diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java index 29a6db6a12a0..8237b20260ea 100644 --- a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java +++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java @@ -105,6 +105,21 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService"; + // TODO(339594686): make API + /** + * @hide + */ + public static final String REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY = + "register_model_update_callback"; + /** + * @hide + */ + public static final String MODEL_LOADED_BUNDLE_KEY = "model_loaded"; + /** + * @hide + */ + public static final String MODEL_UNLOADED_BUNDLE_KEY = "model_unloaded"; + private IRemoteStorageService mRemoteStorageService; /** diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java index db665a92ec5c..c4becea462d5 100644 --- a/core/java/android/view/DisplayCutout.java +++ b/core/java/android/view/DisplayCutout.java @@ -1392,10 +1392,6 @@ public final class DisplayCutout { private static Rect computeSafeInsets(int displayW, int displayH, Insets waterFallInsets, Rect[] bounds) { - if (displayW == displayH) { - throw new UnsupportedOperationException("not implemented: display=" + displayW + "x" - + displayH + " bounding rects=" + Arrays.toString(bounds)); - } int leftInset = Math.max(waterFallInsets.left, findCutoutInsetForSide( displayW, displayH, bounds[BOUNDS_POSITION_LEFT], Gravity.LEFT)); diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl index 1c0834fb22b9..374303501ffc 100644 --- a/core/java/android/view/IWindow.aidl +++ b/core/java/android/view/IWindow.aidl @@ -108,11 +108,6 @@ oneway interface IWindow { void dispatchDragEvent(in DragEvent event); /** - * Pointer icon events - */ - void updatePointerIcon(float x, float y); - - /** * Called for non-application windows when the enter animation has completed. */ void dispatchWindowShown(); @@ -128,4 +123,9 @@ oneway interface IWindow { * @param callbacks to receive responses */ void requestScrollCapture(in IScrollCaptureResponseListener callbacks); + + /** + * Dump the details of a window. + */ + void dumpWindow(in ParcelFileDescriptor pfd); } diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 86264eb31663..e3e4fc04098b 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -288,8 +288,6 @@ interface IWindowSession { oneway void finishMovingTask(IWindow window); - oneway void updatePointerIcon(IWindow window); - /** * Update a tap exclude region identified by provided id in the window. Touches on this region * will neither be dispatched to this window nor change the focus to this window. Passing an diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 60ad926f2be1..1cb276568244 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -30695,21 +30695,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public void setPointerIcon(PointerIcon pointerIcon) { mMousePointerIcon = pointerIcon; - if (com.android.input.flags.Flags.enablePointerChoreographer()) { - final ViewRootImpl viewRootImpl = getViewRootImpl(); - if (viewRootImpl == null) { - return; - } - viewRootImpl.refreshPointerIcon(); - } else { - if (mAttachInfo == null || mAttachInfo.mHandlingPointerEvent) { - return; - } - try { - mAttachInfo.mSession.updatePointerIcon(mAttachInfo.mWindow); - } catch (RemoteException e) { - } + final ViewRootImpl viewRootImpl = getViewRootImpl(); + if (viewRootImpl == null) { + return; } + viewRootImpl.refreshPointerIcon(); } /** diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index fdf3cb119a42..155c0537b5b5 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -74,7 +74,6 @@ import static android.view.ViewRootImplProto.WIDTH; import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES; import static android.view.ViewRootImplProto.WIN_FRAME; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; @@ -96,6 +95,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_V import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OVERRIDE_LAYOUT_IN_DISPLAY_CUTOUT_MODE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; @@ -122,7 +122,6 @@ import static android.view.flags.Flags.toolkitSetFrameRateReadOnly; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER; -import static com.android.input.flags.Flags.enablePointerChoreographer; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.window.flags.Flags.activityWindowInfoFlag; import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay; @@ -195,6 +194,7 @@ import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; @@ -268,11 +268,15 @@ import com.android.internal.inputmethod.InputMethodDebug; import com.android.internal.os.IResultReceiver; import com.android.internal.os.SomeArgs; import com.android.internal.policy.PhoneFallbackEventHandler; +import com.android.internal.util.FastPrintWriter; import com.android.internal.view.BaseSurfaceHolder; import com.android.internal.view.RootViewSurfaceTaker; import com.android.internal.view.SurfaceCallbackHelper; import com.android.modules.expresslog.Counter; +import libcore.io.IoUtils; + +import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; @@ -423,6 +427,12 @@ public final class ViewRootImpl implements ViewParent, private static final long NANOS_PER_SEC = 1000000000; + // If the ViewRootImpl has been idle for more than 200ms, clear the preferred + // frame rate category and frame rate. + private static final int IDLE_TIME_MILLIS = 250; + + private static final long NANOS_PER_MILLI = 1_000_000; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>(); @@ -655,6 +665,8 @@ public final class ViewRootImpl implements ViewParent, private int mMinusOneFrameIntervalMillis = 0; // VRR interval between the previous and the frame before private int mMinusTwoFrameIntervalMillis = 0; + // VRR has the invalidation idle message been posted? + private boolean mInvalidationIdleMessagePosted = false; /** * Update the Choreographer's FrameInfo object with the timing information for the current @@ -4266,6 +4278,10 @@ public final class ViewRootImpl implements ViewParent, // when the values are applicable. if (mDrawnThisFrame) { mDrawnThisFrame = false; + if (!mInvalidationIdleMessagePosted) { + mInvalidationIdleMessagePosted = true; + mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, IDLE_TIME_MILLIS); + } setCategoryFromCategoryCounts(); updateInfrequentCount(); setPreferredFrameRate(mPreferredFrameRate); @@ -6507,6 +6523,8 @@ public final class ViewRootImpl implements ViewParent, return "MSG_WINDOW_TOUCH_MODE_CHANGED"; case MSG_KEEP_CLEAR_RECTS_CHANGED: return "MSG_KEEP_CLEAR_RECTS_CHANGED"; + case MSG_CHECK_INVALIDATION_IDLE: + return "MSG_CHECK_INVALIDATION_IDLE"; case MSG_REFRESH_POINTER_ICON: return "MSG_REFRESH_POINTER_ICON"; case MSG_TOUCH_BOOST_TIMEOUT: @@ -6771,6 +6789,30 @@ public final class ViewRootImpl implements ViewParent, mNumPausedForSync = 0; scheduleTraversals(); break; + case MSG_CHECK_INVALIDATION_IDLE: { + long delta; + if (mIsTouchBoosting || mIsFrameRateBoosting || mInsetsAnimationRunning) { + delta = 0; + } else { + delta = System.nanoTime() / NANOS_PER_MILLI - mLastUpdateTimeMillis; + } + if (delta >= IDLE_TIME_MILLIS) { + mFrameRateCategoryHighCount = 0; + mFrameRateCategoryHighHintCount = 0; + mFrameRateCategoryNormalCount = 0; + mFrameRateCategoryLowCount = 0; + mPreferredFrameRate = 0; + mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; + setPreferredFrameRateCategory(FRAME_RATE_CATEGORY_NO_PREFERENCE); + setPreferredFrameRate(0f); + mInvalidationIdleMessagePosted = false; + } else { + mInvalidationIdleMessagePosted = true; + mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, + IDLE_TIME_MILLIS - delta); + } + break; + } case MSG_TOUCH_BOOST_TIMEOUT: /** * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME). @@ -7937,46 +7979,20 @@ public final class ViewRootImpl implements ViewParent, if (event.isStylusPointer() && mIsStylusPointerIconEnabled) { pointerIcon = mHandwritingInitiator.onResolvePointerIcon(mContext, event); } - if (pointerIcon == null) { pointerIcon = mView.onResolvePointerIcon(event, pointerIndex); } - - if (enablePointerChoreographer()) { - if (pointerIcon == null) { - pointerIcon = PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED); - } - if (Objects.equals(mResolvedPointerIcon, pointerIcon)) { - return true; - } - mResolvedPointerIcon = pointerIcon; - - InputManagerGlobal.getInstance() - .setPointerIcon(pointerIcon, event.getDisplayId(), - event.getDeviceId(), event.getPointerId(0), getInputToken()); + if (pointerIcon == null) { + pointerIcon = PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED); + } + if (Objects.equals(mResolvedPointerIcon, pointerIcon)) { return true; } + mResolvedPointerIcon = pointerIcon; - final int pointerType = (pointerIcon != null) ? - pointerIcon.getType() : PointerIcon.TYPE_NOT_SPECIFIED; - - if (mPointerIconType == null || mPointerIconType != pointerType) { - mPointerIconType = pointerType; - mCustomPointerIcon = null; - if (mPointerIconType != PointerIcon.TYPE_CUSTOM) { - InputManagerGlobal - .getInstance() - .setPointerIconType(pointerType); - return true; - } - } - if (mPointerIconType == PointerIcon.TYPE_CUSTOM && - !pointerIcon.equals(mCustomPointerIcon)) { - mCustomPointerIcon = pointerIcon; - InputManagerGlobal - .getInstance() - .setCustomPointerIcon(mCustomPointerIcon); - } + InputManagerGlobal.getInstance() + .setPointerIcon(pointerIcon, event.getDisplayId(), + event.getDeviceId(), event.getPointerId(0), getInputToken()); return true; } @@ -10580,16 +10596,6 @@ public final class ViewRootImpl implements ViewParent, mHandler.sendMessage(msg); } - public void updatePointerIcon(float x, float y) { - final int what = MSG_UPDATE_POINTER_ICON; - mHandler.removeMessages(what); - final long now = SystemClock.uptimeMillis(); - final MotionEvent event = MotionEvent.obtain( - 0, now, MotionEvent.ACTION_HOVER_MOVE, x, y, 0); - Message msg = mHandler.obtainMessage(what, event); - mHandler.sendMessage(msg); - } - public void dispatchCheckFocus() { if (!mHandler.hasMessages(MSG_CHECK_FOCUS)) { // This will result in a call to checkFocus() below. @@ -11453,14 +11459,6 @@ public final class ViewRootImpl implements ViewParent, } @Override - public void updatePointerIcon(float x, float y) { - final ViewRootImpl viewAncestor = mViewAncestor.get(); - if (viewAncestor != null) { - viewAncestor.updatePointerIcon(x, y); - } - } - - @Override public void dispatchWindowShown() { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { @@ -11483,6 +11481,26 @@ public final class ViewRootImpl implements ViewParent, viewAncestor.dispatchScrollCaptureRequest(listener); } } + + @Override + public void dumpWindow(ParcelFileDescriptor pfd) { + final ViewRootImpl viewAncestor = mViewAncestor.get(); + if (viewAncestor == null) { + return; + } + viewAncestor.mHandler.postAtFrontOfQueue(() -> { + final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); + try { + PrintWriter pw = new FastPrintWriter(new FileOutputStream( + pfd.getFileDescriptor())); + viewAncestor.dump("", pw); + pw.flush(); + } finally { + IoUtils.closeQuietly(pfd); + StrictMode.setThreadPolicy(oldPolicy); + } + }); + } } public static final class CalledFromWrongThreadException extends AndroidRuntimeException { @@ -13016,6 +13034,10 @@ public final class ViewRootImpl implements ViewParent, private void removeVrrMessages() { mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT); mHandler.removeMessages(MSG_FRAME_RATE_SETTING); + if (mInvalidationIdleMessagePosted) { + mInvalidationIdleMessagePosted = false; + mHandler.removeMessages(MSG_CHECK_INVALIDATION_IDLE); + } } /** diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index e6367ff7b4dc..d7d764b6ce75 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -603,10 +603,6 @@ public class WindowlessWindowManager implements IWindowSession { } @Override - public void updatePointerIcon(android.view.IWindow window) { - } - - @Override public void updateTapExcludeRegion(android.view.IWindow window, android.graphics.Region region) { } diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index bfe4e6f3cc9b..9cc4191d0c80 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -1978,6 +1978,13 @@ public final class AutofillManager { if (Objects.equals(mLastAutofilledData.get(id), value)) { view.setAutofilled(true, hideHighlight); + try { + mService.setViewAutofilled(mSessionId, id, mContext.getUserId()); + } catch (RemoteException e) { + // The failure could be a consequence of something going wrong on the + // server side. Do nothing here since it's just logging, but it's + // possible follow-up actions may fail. + } } else { view.setAutofilled(false, false); mLastAutofilledData.remove(id); @@ -2978,6 +2985,13 @@ public final class AutofillManager { mLastAutofilledData.put(view.getAutofillId(), targetValue); } view.setAutofilled(true, hideHighlight); + try { + mService.setViewAutofilled(mSessionId, view.getAutofillId(), mContext.getUserId()); + } catch (RemoteException e) { + // The failure could be a consequence of something going wrong on the server side. + // Do nothing here since it's just logging, but it's possible follow-up actions may + // fail. + } } } diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl index 1a9322e6f2b8..2039b4d5c1bd 100644 --- a/core/java/android/view/autofill/IAutoFillManager.aidl +++ b/core/java/android/view/autofill/IAutoFillManager.aidl @@ -49,6 +49,7 @@ oneway interface IAutoFillManager { void updateSession(int sessionId, in AutofillId id, in Rect bounds, in AutofillValue value, int action, int flags, int userId); void setAutofillFailure(int sessionId, in List<AutofillId> ids, int userId); + void setViewAutofilled(int sessionId, in AutofillId id, int userId); void finishSession(int sessionId, int userId, int commitReason); void cancelSession(int sessionId, int userId); void setAuthenticationResult(in Bundle data, int sessionId, int authenticationId, int userId); diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java index c5114b9550db..fb3e0831fdc9 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -720,6 +720,20 @@ public class EditorInfo implements InputType, Parcelable { private boolean mIsStylusHandwritingEnabled; + + /** + * AndroidX Core library 1.13.0 introduced EditorInfoCompat#setStylusHandwritingEnabled and + * EditorInfoCompat#isStylusHandwritingEnabled which used a boolean value in the EditorInfo + * extras bundle. These methods do not set or check the Android V property since the Android V + * SDK was not yet available. In order for EditorInfoCompat#isStylusHandwritingEnabled to return + * the correct value for EditorInfo created by Android V TextView, the extras bundle value + * should be set. This is the extras bundle key. + * + * @hide + */ + public static final String STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY = + "androidx.core.view.inputmethod.EditorInfoCompat.STYLUS_HANDWRITING_ENABLED"; + /** * Set {@code true} if the {@code Editor} has * {@link InputMethodManager#startStylusHandwriting stylus handwriting} enabled. diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index a073873cb2a0..cf128fbaf50f 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2530,18 +2530,6 @@ public final class InputMethodManager { view, /* delegatorPackageName= */ null, /* handwritingDelegateFlags= */ 0); } - private void startStylusHandwritingInternalAsync( - @NonNull View view, @Nullable String delegatorPackageName, - @HandwritingDelegateFlags int handwritingDelegateFlags, - @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) { - Objects.requireNonNull(view); - Objects.requireNonNull(executor); - Objects.requireNonNull(callback); - - startStylusHandwritingInternal( - view, delegatorPackageName, handwritingDelegateFlags, executor, callback); - } - private void sendFailureCallback(@NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) { if (executor == null || callback == null) { @@ -2891,7 +2879,7 @@ public final class InputMethodManager { if (Flags.homeScreenHandwritingDelegator()) { flags = delegateView.getHandwritingDelegateFlags(); } - startStylusHandwritingInternalAsync( + acceptStylusHandwritingDelegation( delegateView, delegatorPackageName, flags, executor, callback); } @@ -2926,6 +2914,9 @@ public final class InputMethodManager { @HandwritingDelegateFlags int flags, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) { Objects.requireNonNull(delegatorPackageName); + Objects.requireNonNull(delegateView); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); startStylusHandwritingInternal( delegateView, delegatorPackageName, flags, executor, callback); diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig index fa9458d01681..56e5bcf79933 100644 --- a/core/java/android/view/inputmethod/flags.aconfig +++ b/core/java/android/view/inputmethod/flags.aconfig @@ -62,6 +62,17 @@ flag { } flag { + name: "use_input_method_info_safe_list" + namespace: "input_method" + description: "Use InputMethodInfoSafeList for more reliable binder IPCs" + bug: "339761278" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "ime_switcher_revamp" is_exported: true namespace: "input_method" diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java index a07141b260ee..b7ee0b8a238a 100644 --- a/core/java/android/webkit/WebChromeClient.java +++ b/core/java/android/webkit/WebChromeClient.java @@ -520,6 +520,13 @@ public class WebChromeClient { * To cancel the request, call <code>filePathCallback.onReceiveValue(null)</code> and * return {@code true}. * + * <p class="note"><b>Note:</b> WebView does not enforce any restrictions on + * the chosen file(s). WebView can access all files that your app can access. + * In case the file(s) are chosen through an untrusted source such as a third-party + * app, it is your own app's responsibility to check what the returned Uris + * refer to before calling the <code>filePathCallback</code>. See + * {@link #createIntent} and {@link #parseResult} for more details.</p> + * * @param webView The WebView instance that is initiating the request. * @param filePathCallback Invoke this callback to supply the list of paths to files to upload, * or {@code null} to cancel. Must only be called if the @@ -556,6 +563,15 @@ public class WebChromeClient { * Parse the result returned by the file picker activity. This method should be used with * {@link #createIntent}. Refer to {@link #createIntent} for how to use it. * + * <p class="note"><b>Note:</b> The intent returned by the file picker activity + * should be treated as untrusted. A third-party app handling the implicit + * intent created by {@link #createIntent} might return Uris that the third-party + * app itself does not have access to, such as your own app's sensitive data files. + * WebView does not enforce any restrictions on the returned Uris. It is the + * app's responsibility to ensure that the untrusted source (such as a third-party + * app) has access the Uris it has returned and that the Uris are not pointing + * to any sensitive data files.</p> + * * @param resultCode the integer result code returned by the file picker activity. * @param data the intent returned by the file picker activity. * @return the Uris of selected file(s) or {@code null} if the resultCode indicates @@ -618,6 +634,12 @@ public class WebChromeClient { * WebChromeClient#onShowFileChooser}</li> * </ol> * + * <p class="note"><b>Note:</b> The created intent may be handled by + * third-party applications on device. The received result must be treated + * as untrusted as it can contain Uris pointing to your own app's sensitive + * data files. Your app should check the resultant Uris in {@link #parseResult} + * before calling the <code>filePathCallback</code>.</p> + * * @return an Intent that supports basic file chooser sources. */ public abstract Intent createIntent(); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index fb1c331171a6..78dd3b18c2a6 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -27,6 +27,7 @@ import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_C import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX; import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY; import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION; +import static android.view.inputmethod.EditorInfo.STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY; import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE; import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH; @@ -10062,9 +10063,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); outAttrs.setInitialSurroundingText(mText); outAttrs.contentMimeTypes = getReceiveContentMimeTypes(); - if (android.view.inputmethod.Flags.editorinfoHandwritingEnabled() - && isAutoHandwritingEnabled()) { - outAttrs.setStylusHandwritingEnabled(true); + if (android.view.inputmethod.Flags.editorinfoHandwritingEnabled()) { + boolean handwritingEnabled = isAutoHandwritingEnabled(); + outAttrs.setStylusHandwritingEnabled(handwritingEnabled); + // AndroidX Core library 1.13.0 introduced + // EditorInfoCompat#setStylusHandwritingEnabled and + // EditorInfoCompat#isStylusHandwritingEnabled which used a boolean value in the + // EditorInfo extras bundle. These methods do not set or check the Android V + // property since the Android V SDK was not yet available. In order for + // EditorInfoCompat#isStylusHandwritingEnabled to return the correct value for + // EditorInfo created by Android V TextView, the extras bundle value is also set + // here. + if (outAttrs.extras == null) { + outAttrs.extras = new Bundle(); + } + outAttrs.extras.putBoolean( + STYLUS_HANDWRITING_ENABLED_ANDROIDX_EXTRAS_KEY, handwritingEnabled); } ArrayList<Class<? extends HandwritingGesture>> gestures = new ArrayList<>(); gestures.add(SelectGesture.class); diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java index 64fe66e36bc3..ec4e3e9163fa 100644 --- a/core/java/android/window/TransitionFilter.java +++ b/core/java/android/window/TransitionFilter.java @@ -25,6 +25,7 @@ import android.annotation.Nullable; import android.app.ActivityManager; import android.app.WindowConfiguration; import android.content.ComponentName; +import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.view.WindowManager; @@ -180,6 +181,7 @@ public final class TransitionFilter implements Parcelable { public @ContainerOrder int mOrder = CONTAINER_ORDER_ANY; public ComponentName mTopActivity; + public IBinder mLaunchCookie; public Requirement() { } @@ -193,6 +195,7 @@ public final class TransitionFilter implements Parcelable { mMustBeTask = in.readBoolean(); mOrder = in.readInt(); mTopActivity = in.readTypedObject(ComponentName.CREATOR); + mLaunchCookie = in.readStrongBinder(); } /** Go through changes and find if at-least one change matches this filter */ @@ -231,6 +234,9 @@ public final class TransitionFilter implements Parcelable { if (mMustBeTask && change.getTaskInfo() == null) { continue; } + if (!matchesCookie(change.getTaskInfo())) { + continue; + } return true; } return false; @@ -247,13 +253,25 @@ public final class TransitionFilter implements Parcelable { return false; } + private boolean matchesCookie(ActivityManager.RunningTaskInfo info) { + if (mLaunchCookie == null) return true; + if (info == null) return false; + for (IBinder cookie : info.launchCookies) { + if (mLaunchCookie.equals(cookie)) { + return true; + } + } + return false; + } + /** Check if the request matches this filter. It may generate false positives */ boolean matches(@NonNull TransitionRequestInfo request) { // Can't check modes/order since the transition hasn't been built at this point. if (mActivityType == ACTIVITY_TYPE_UNDEFINED) return true; return request.getTriggerTask() != null && request.getTriggerTask().getActivityType() == mActivityType - && matchesTopActivity(request.getTriggerTask(), null /* activityCmp */); + && matchesTopActivity(request.getTriggerTask(), null /* activityCmp */) + && matchesCookie(request.getTriggerTask()); } @Override @@ -267,6 +285,7 @@ public final class TransitionFilter implements Parcelable { dest.writeBoolean(mMustBeTask); dest.writeInt(mOrder); dest.writeTypedObject(mTopActivity, flags); + dest.writeStrongBinder(mLaunchCookie); } @NonNull @@ -307,6 +326,7 @@ public final class TransitionFilter implements Parcelable { out.append(" mustBeTask=" + mMustBeTask); out.append(" order=" + containerOrderToString(mOrder)); out.append(" topActivity=").append(mTopActivity); + out.append(" launchCookie=").append(mLaunchCookie); out.append("}"); return out.toString(); } diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index f931a762871c..e29f256146d0 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -47,7 +47,7 @@ oneway interface IStatusBar void animateExpandNotificationsPanel(); void animateExpandSettingsPanel(String subPanel); void animateCollapsePanels(); - void togglePanel(); + void toggleNotificationsPanel(); void showWirelessChargingAnimation(int batteryLevel); diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index b83b2d20c561..fc60f065a965 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -17,7 +17,6 @@ package com.android.internal.statusbar; import android.app.Notification; -import android.app.StatusBarManager; import android.content.ComponentName; import android.graphics.drawable.Icon; import android.graphics.Rect; @@ -53,9 +52,9 @@ interface IStatusBarService void togglePanel(); @UnsupportedAppUsage void disable(int what, IBinder token, String pkg); + void disableForUser(int what, IBinder token, String pkg, int userId); void disable2(int what, IBinder token, String pkg); - void disableForUser(in StatusBarManager.DisableInfo info, IBinder token, String pkg, int userId, String reason); - + void disable2ForUser(int what, IBinder token, String pkg, int userId); int[] getDisableFlags(IBinder token, int userId); void setIcon(String slot, String iconPackage, int iconId, int iconLevel, String contentDescription); @UnsupportedAppUsage diff --git a/core/java/com/android/internal/util/NewlineNormalizer.java b/core/java/com/android/internal/util/NewlineNormalizer.java new file mode 100644 index 000000000000..0104d1f56f83 --- /dev/null +++ b/core/java/com/android/internal/util/NewlineNormalizer.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util; + + +import java.util.regex.Pattern; + +/** + * Utility class that replaces consecutive empty lines with single new line. + * @hide + */ +public class NewlineNormalizer { + + private static final Pattern MULTIPLE_NEWLINES = Pattern.compile("\\v(\\s*\\v)?"); + + // Private constructor to prevent instantiation + private NewlineNormalizer() {} + + /** + * Replaces consecutive newlines with a single newline in the input text. + */ + public static String normalizeNewlines(String text) { + return MULTIPLE_NEWLINES.matcher(text).replaceAll("\n"); + } +} diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java index e33704b0c535..3fc4fff21d2d 100644 --- a/core/java/com/android/internal/view/BaseIWindow.java +++ b/core/java/com/android/internal/view/BaseIWindow.java @@ -18,7 +18,6 @@ package com.android.internal.view; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; -import android.hardware.input.InputManagerGlobal; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; @@ -29,7 +28,6 @@ import android.view.IWindow; import android.view.IWindowSession; import android.view.InsetsSourceControl; import android.view.InsetsState; -import android.view.PointerIcon; import android.view.ScrollCaptureResponse; import android.view.WindowInsets.Type.InsetsType; import android.view.inputmethod.ImeTracker; @@ -128,12 +126,6 @@ public class BaseIWindow extends IWindow.Stub { } @Override - public void updatePointerIcon(float x, float y) { - InputManagerGlobal.getInstance() - .setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED); - } - - @Override public void dispatchWallpaperCommand(String action, int x, int y, int z, Bundle extras, boolean sync) { if (sync) { @@ -162,4 +154,9 @@ public class BaseIWindow extends IWindow.Stub { // ignore } } + + @Override + public void dumpWindow(ParcelFileDescriptor pfd) { + + } } diff --git a/core/jni/android_hardware_display_DisplayViewport.cpp b/core/jni/android_hardware_display_DisplayViewport.cpp index 7f630cb27972..5d7b33e89d19 100644 --- a/core/jni/android_hardware_display_DisplayViewport.cpp +++ b/core/jni/android_hardware_display_DisplayViewport.cpp @@ -59,7 +59,8 @@ status_t android_hardware_display_DisplayViewport_toNative(JNIEnv* env, jobject static const jclass intClass = FindClassOrDie(env, "java/lang/Integer"); static const jmethodID byteValue = env->GetMethodID(intClass, "byteValue", "()B"); - viewport->displayId = env->GetIntField(viewportObj, gDisplayViewportClassInfo.displayId); + viewport->displayId = ui::LogicalDisplayId{ + env->GetIntField(viewportObj, gDisplayViewportClassInfo.displayId)}; viewport->isActive = env->GetBooleanField(viewportObj, gDisplayViewportClassInfo.isActive); jint orientation = env->GetIntField(viewportObj, gDisplayViewportClassInfo.orientation); viewport->orientation = static_cast<ui::Rotation>(orientation); diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp index bed776836043..69f633420a0d 100644 --- a/core/jni/android_hardware_input_InputWindowHandle.cpp +++ b/core/jni/android_hardware_input_InputWindowHandle.cpp @@ -165,8 +165,8 @@ bool NativeInputWindowHandle::updateInfo() { mInfo.ownerUid = gui::Uid{ static_cast<uid_t>(env->GetIntField(obj, gInputWindowHandleClassInfo.ownerUid))}; mInfo.packageName = getStringField(env, obj, gInputWindowHandleClassInfo.packageName, "<null>"); - mInfo.displayId = env->GetIntField(obj, - gInputWindowHandleClassInfo.displayId); + mInfo.displayId = + ui::LogicalDisplayId{env->GetIntField(obj, gInputWindowHandleClassInfo.displayId)}; jobject inputApplicationHandleObj = env->GetObjectField(obj, gInputWindowHandleClassInfo.inputApplicationHandle); diff --git a/core/jni/android_view_KeyEvent.cpp b/core/jni/android_view_KeyEvent.cpp index ca8752f93e11..06e0d2d3ceaa 100644 --- a/core/jni/android_view_KeyEvent.cpp +++ b/core/jni/android_view_KeyEvent.cpp @@ -135,8 +135,8 @@ KeyEvent android_view_KeyEvent_obtainAsCopy(JNIEnv* env, jobject eventObj) { jlong eventTime = env->GetLongField(eventObj, gKeyEventClassInfo.mEventTime); KeyEvent event; - event.initialize(id, deviceId, source, displayId, *hmac, action, flags, keyCode, scanCode, - metaState, repeatCount, downTime, eventTime); + event.initialize(id, deviceId, source, ui::LogicalDisplayId{displayId}, *hmac, action, flags, + keyCode, scanCode, metaState, repeatCount, downTime, eventTime); return event; } diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp index 3e3af40a6530..f914bee8da1b 100644 --- a/core/jni/android_view_MotionEvent.cpp +++ b/core/jni/android_view_MotionEvent.cpp @@ -22,7 +22,6 @@ #include <android_runtime/AndroidRuntime.h> #include <android_runtime/Log.h> #include <attestation/HmacKeyManager.h> -#include <gui/constants.h> #include <input/Input.h> #include <log/log.h> #include <nativehelper/JNIHelp.h> @@ -367,8 +366,8 @@ static jlong android_view_MotionEvent_nativeInitialize( ui::Transform transform; transform.set(xOffset, yOffset); ui::Transform identityTransform; - event->initialize(InputEvent::nextId(), deviceId, source, displayId, INVALID_HMAC, action, 0, - flags, edgeFlags, metaState, buttonState, + event->initialize(InputEvent::nextId(), deviceId, source, ui::LogicalDisplayId{displayId}, + INVALID_HMAC, action, 0, flags, edgeFlags, metaState, buttonState, static_cast<MotionClassification>(classification), transform, xPrecision, yPrecision, AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, downTimeNanos, @@ -646,13 +645,13 @@ static void android_view_MotionEvent_nativeSetSource(CRITICAL_JNI_PARAMS_COMMA j static jint android_view_MotionEvent_nativeGetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) { MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr); - return event->getDisplayId(); + return static_cast<jint>(event->getDisplayId().val()); } static void android_view_MotionEvent_nativeSetDisplayId(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr, jint displayId) { MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr); - return event->setDisplayId(displayId); + event->setDisplayId(ui::LogicalDisplayId{displayId}); } static jint android_view_MotionEvent_nativeGetAction(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) { diff --git a/core/jni/android_window_WindowInfosListener.cpp b/core/jni/android_window_WindowInfosListener.cpp index bc69d1e67668..c39d5e20aa1c 100644 --- a/core/jni/android_window_WindowInfosListener.cpp +++ b/core/jni/android_window_WindowInfosListener.cpp @@ -60,7 +60,7 @@ jobject fromDisplayInfo(JNIEnv* env, gui::DisplayInfo displayInfo) { } ScopedLocalRef<jobject> matrixObj(env, AMatrix_newInstance(env, transformValues)); return env->NewObject(gDisplayInfoClassInfo.clazz, gDisplayInfoClassInfo.ctor, - displayInfo.displayId, displayInfo.logicalWidth, + displayInfo.displayId.val(), displayInfo.logicalWidth, displayInfo.logicalHeight, matrixObj.get()); } diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 12d62ccf97b6..062fab38656d 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -116,7 +116,7 @@ using android::base::GetBoolProperty; using android::zygote::ZygoteFailure; -using Action = android_mallopt_gwp_asan_options_t::Action; +using Mode = android_mallopt_gwp_asan_options_t::Mode; // This type is duplicated in fd_utils.h typedef const std::function<void(std::string)>& fail_fn_t; @@ -2101,21 +2101,21 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, switch (runtime_flags & RuntimeFlags::GWP_ASAN_LEVEL_MASK) { default: case RuntimeFlags::GWP_ASAN_LEVEL_DEFAULT: - gwp_asan_options.desire = GetBoolProperty(kGwpAsanAppRecoverableSysprop, true) - ? Action::TURN_ON_FOR_APP_SAMPLED_NON_CRASHING - : Action::DONT_TURN_ON_UNLESS_OVERRIDDEN; + gwp_asan_options.mode = GetBoolProperty(kGwpAsanAppRecoverableSysprop, true) + ? Mode::APP_MANIFEST_DEFAULT + : Mode::APP_MANIFEST_NEVER; android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options)); break; case RuntimeFlags::GWP_ASAN_LEVEL_NEVER: - gwp_asan_options.desire = Action::DONT_TURN_ON_UNLESS_OVERRIDDEN; + gwp_asan_options.mode = Mode::APP_MANIFEST_NEVER; android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options)); break; case RuntimeFlags::GWP_ASAN_LEVEL_ALWAYS: - gwp_asan_options.desire = Action::TURN_ON_FOR_APP; + gwp_asan_options.mode = Mode::APP_MANIFEST_ALWAYS; android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options)); break; case RuntimeFlags::GWP_ASAN_LEVEL_LOTTERY: - gwp_asan_options.desire = Action::TURN_ON_WITH_SAMPLING; + gwp_asan_options.mode = Mode::APP_MANIFEST_DEFAULT; android_mallopt(M_INITIALIZE_GWP_ASAN, &gwp_asan_options, sizeof(gwp_asan_options)); break; } diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp index 043385513027..bf2fddab3d41 100644 --- a/core/jni/platform/host/HostRuntime.cpp +++ b/core/jni/platform/host/HostRuntime.cpp @@ -329,7 +329,8 @@ static void init_keyboard(JNIEnv* env, const vector<string>& keyboardPaths) { InputDeviceInfo info = InputDeviceInfo(); info.initialize(keyboardId, 0, 0, InputDeviceIdentifier(), - "keyboard " + std::to_string(keyboardId), true, false, 0); + "keyboard " + std::to_string(keyboardId), true, false, + ui::ADISPLAY_ID_DEFAULT); info.setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC); info.setKeyCharacterMap(*charMap); diff --git a/core/res/res/drawable/ic_signal_cellular_1_4_bar.xml b/core/res/res/drawable/ic_signal_cellular_1_4_bar.xml index c0fe536cf85b..7c45c20a758b 100644 --- a/core/res/res/drawable/ic_signal_cellular_1_4_bar.xml +++ b/core/res/res/drawable/ic_signal_cellular_1_4_bar.xml @@ -22,7 +22,11 @@ <path android:fillColor="@android:color/white" android:pathData="M20,7v13H7L20,7 M22,2L2,22h20V2L22,2z" /> - <path - android:fillColor="@android:color/white" - android:pathData="M 11 13 L 2 22 L 11 22 Z" /> + <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z"> + <!-- 1 bar. move to higher ground. --> + <path + android:name="ic_signal_cellular_1_4_bar" + android:fillColor="@android:color/white" + android:pathData="M6,0 H11 V20 H6 z" /> + </clip-path> </vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_signal_cellular_1_5_bar.xml b/core/res/res/drawable/ic_signal_cellular_1_5_bar.xml index 816da22cf7dc..02b646d310e5 100644 --- a/core/res/res/drawable/ic_signal_cellular_1_5_bar.xml +++ b/core/res/res/drawable/ic_signal_cellular_1_5_bar.xml @@ -22,7 +22,11 @@ <path android:fillColor="@android:color/white" android:pathData="M20,7V20H7L20,7m2-5L2,22H22V2Z" /> - <path - android:fillColor="@android:color/white" - android:pathData="M8.72,15.28,2,22H8.72V15.28Z" /> + <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z"> + <!-- 1 bar. might have to call you back. --> + <path + android:name="ic_signal_cellular_1_5_bar" + android:fillColor="@android:color/white" + android:pathData="M6,0 H12 V20 H6 z" /> + </clip-path> </vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_signal_cellular_2_4_bar.xml b/core/res/res/drawable/ic_signal_cellular_2_4_bar.xml index 69a966ba4fc9..514d1690abcf 100644 --- a/core/res/res/drawable/ic_signal_cellular_2_4_bar.xml +++ b/core/res/res/drawable/ic_signal_cellular_2_4_bar.xml @@ -22,7 +22,11 @@ <path android:fillColor="@android:color/white" android:pathData="M20,7v13H7L20,7 M22,2L2,22h20V2L22,2z" /> - <path - android:fillColor="@android:color/white" - android:pathData="M 13 11 L 2 22 L 13 22 Z" /> + <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z"> + <!-- 2 bars. 2 out of 4 ain't bad. --> + <path + android:name="ic_signal_cellular_2_4_bar" + android:fillColor="@android:color/white" + android:pathData="M6,0 H14 V20 H6 z" /> + </clip-path> </vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_signal_cellular_2_5_bar.xml b/core/res/res/drawable/ic_signal_cellular_2_5_bar.xml index 02c7a430cece..a97f771a6632 100644 --- a/core/res/res/drawable/ic_signal_cellular_2_5_bar.xml +++ b/core/res/res/drawable/ic_signal_cellular_2_5_bar.xml @@ -23,7 +23,11 @@ <path android:fillColor="@android:color/white" android:pathData="M20,7V20H7L20,7m2-5L2,22H22V2Z" /> - <path - android:fillColor="@android:color/white" - android:pathData="M 11.45 12.55 L 2 22 L 11.45 22 L 11.45 12.55 Z" /> + <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z"> + <!-- 2 bars. hanging in there. --> + <path + android:name="ic_signal_cellular_2_5_bar" + android:fillColor="@android:color/white" + android:pathData="M6,0 H14 V20 H6 z" /> + </clip-path> </vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_signal_cellular_3_4_bar.xml b/core/res/res/drawable/ic_signal_cellular_3_4_bar.xml index 46ce47cdfcda..1bacf4ad678f 100644 --- a/core/res/res/drawable/ic_signal_cellular_3_4_bar.xml +++ b/core/res/res/drawable/ic_signal_cellular_3_4_bar.xml @@ -22,7 +22,11 @@ <path android:fillColor="@android:color/white" android:pathData="M20,7v13H7L20,7 M22,2L2,22h20V2L22,2z" /> - <path - android:fillColor="@android:color/white" - android:pathData="M 2 22 L 16 22 L 16 21 L 16 20 L 16 11 L 16 10 L 16 8 Z" /> + <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z"> + <!-- 3 bars. quite nice. --> + <path + android:name="ic_signal_cellular_3_4_bar" + android:fillColor="@android:color/white" + android:pathData="M6,0 H17 V20 H6 z" /> + </clip-path> </vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_signal_cellular_3_5_bar.xml b/core/res/res/drawable/ic_signal_cellular_3_5_bar.xml index 37435e6b75e2..2789d3e9305c 100644 --- a/core/res/res/drawable/ic_signal_cellular_3_5_bar.xml +++ b/core/res/res/drawable/ic_signal_cellular_3_5_bar.xml @@ -22,7 +22,11 @@ <path android:fillColor="@android:color/white" android:pathData="M20,7V20H7L20,7m2-5L2,22H22V2Z" /> - <path - android:fillColor="@android:color/white" - android:pathData="M 14.96 9.04 L 2 22 L 14.96 22 L 14.96 9.04 Z" /> + <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z"> + <!-- 3 bars. not great, not terrible. --> + <path + android:name="ic_signal_cellular_3_5_bar" + android:fillColor="@android:color/white" + android:pathData="M6,0 H16 V20 H6 z" /> + </clip-path> </vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_signal_cellular_4_5_bar.xml b/core/res/res/drawable/ic_signal_cellular_4_5_bar.xml index 6dc3646d89e3..8286dbb5576f 100644 --- a/core/res/res/drawable/ic_signal_cellular_4_5_bar.xml +++ b/core/res/res/drawable/ic_signal_cellular_4_5_bar.xml @@ -22,7 +22,11 @@ <path android:fillColor="@android:color/white" android:pathData="M20,7V20H7L20,7m2-5L2,22H22V2Z" /> - <path - android:fillColor="@android:color/white" - android:pathData="M 18.48 5.52 L 2 22 L 18.48 22 L 18.48 5.52 Z" /> + <clip-path android:name="triangle" android:pathData="M20,7v13H7L20,7z"> + <!-- 4 bars. extremely respectable. --> + <path + android:name="ic_signal_cellular_4_5_bar" + android:fillColor="@android:color/white" + android:pathData="M6,0 H18 V20 H6 z" /> + </clip-path> </vector>
\ No newline at end of file diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index b65fc5f4e890..2a3c691dcd04 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Maak Boodskappe oop"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Hoe dit werk"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Hangend …"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index cef77b026c83..08a290c454f3 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"መልዕክቶች ይክፈቱ"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"እንዴት እንደሚሠራ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"በመጠባበቅ ላይ..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index 2a5822a5fa1c..20d491fd22d8 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -165,8 +165,8 @@ <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"تم الاتصال بشبكة \"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>\" المشفَّرة"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"أصبح الاتصال باستخدام شريحة SIM لشبكة \"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>\" أكثر أمانًا الآن"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"تم الاتصال بشبكة غير مشفَّرة"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"المكالمات والرسائل والبيانات هي أكثر عرضة للاختراق في الوقت الحالي أثناء استخدام شريحة SIM لشبكة \"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>\""</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"المكالمات والرسائل والبيانات هي أكثر عرضة للاختراق في الوقت الحالي أثناء استخدام شريحة SIM لشبكة \"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>\".\n\nستتلقّى إشعارًا آخر عندما يتم تشفير اتصالك مرة أخرى."</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"تكون المكالمات والرسائل والبيانات في الوقت الحالي أكثر عرضة للاختراق أثناء استخدام شريحة SIM من شبكة \"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>\""</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"تكون المكالمات والرسائل والبيانات في الوقت الحالي أكثر عرضة للاختراق أثناء استخدام شريحة SIM من شبكة \"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>\".\n\nستتلقّى إشعارًا آخر عندما يتم تشفير اتصالك مرة أخرى."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"إعدادات أمان شبكة الجوّال"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"مزيد من المعلومات"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"حسنًا"</string> @@ -849,7 +849,7 @@ <string name="policylab_forceLock" msgid="7360335502968476434">"قفل الشاشة"</string> <string name="policydesc_forceLock" msgid="1008844760853899693">"التحكّم في طريقة ووقت قفل الشاشة"</string> <string name="policylab_wipeData" msgid="1359485247727537311">"محو جميع البيانات"</string> - <string name="policydesc_wipeData" product="tablet" msgid="7245372676261947507">"يمكنك محو بيانات الجهاز اللوحي بدون تحذير، وذلك عبر إجراء إعادة الضبط على الإعدادات الأصلية."</string> + <string name="policydesc_wipeData" product="tablet" msgid="7245372676261947507">"محو بيانات الجهاز اللوحي بدون تحذير، وذلك عبر إعادة الضبط على الإعدادات الأصلية"</string> <string name="policydesc_wipeData" product="tv" msgid="513862488950801261">"يمكنك محو بيانات جهاز Android TV بدون تحذير عن طريق تنفيذ إعادة الضبط على الإعدادات الأصلية."</string> <string name="policydesc_wipeData" product="automotive" msgid="660804547737323300">"يمكنك محو بيانات \"نظام الترفيه والمعلومات\" بدون تحذير، وذلك من خلال إعادة الضبط على الإعدادات الأصلية."</string> <string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"محو بيانات الهاتف بدون تحذير، وذلك من خلال إعادة ضبط البيانات على الإعدادات الأصلية"</string> @@ -2417,4 +2417,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"فتح تطبيق \"الرسائل\""</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"طريقة العمل"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"بانتظار الإزالة من الأرشيف…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml index 6e0da5454350..30ced90bd5e4 100644 --- a/core/res/res/values-as/strings.xml +++ b/core/res/res/values-as/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages খোলক"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ই কেনেকৈ কাম কৰে"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"বিবেচনাধীন হৈ আছে..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml index a3d4423ea772..49073f11f897 100644 --- a/core/res/res/values-az/strings.xml +++ b/core/res/res/values-az/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Mesajı açın"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Haqqında"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Gözləmədə..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml index fff999ac57c4..ca66ef4a018f 100644 --- a/core/res/res/values-b+sr+Latn/strings.xml +++ b/core/res/res/values-b+sr+Latn/strings.xml @@ -161,7 +161,7 @@ <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Mreža u blizini je u <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> evidentirala jedinstveni ID vašeg uređaja (IMSI ili IMEI) dok ste koristili <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM.\n\nTo znači da je evidentirala vašu lokaciju, aktivnost i identitet. To je uobičajena praksa, ali može da bude problem ljudima koji su zabrinuti za privatnost."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Povezani ste na šifrovanu mrežu <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Veza <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM-a je sada bezbednija"</string> - <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Povezani ste na šifrovanu mrežu"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Povezani ste na nešifrovanu mrežu"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Pozivi, poruke i podaci su trenutno ranjiviji dok koristite <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Pozivi, poruke i podaci su trenutno ranjiviji dok koristite <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM.\n\nKada veza ponovo bude šifrovana, poslaćemo vam drugo obaveštenje."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Podešavanja bezbednosti na mobilnoj mreži"</string> @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvori Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Princip rada"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Na čekanju..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index 19ca1c4da054..4cd150a6ebf6 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -162,7 +162,7 @@ <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"У <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> у сетцы паблізу быў запісаны ўнікальны ідэнтыфікатар вашай прылады (IMSI або IMEI) пры выкарыстанні SIM-карты <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nГэта азначае, што даныя пра ваша месцазнаходжанне, дзеянні або асобу былі зарэгістраваны. Гэта звычайная практыка, але можа быць праблемай для людзей, якія турбуюцца аб прыватнасці."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Падключана да зашыфраванай сеткі <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Цяпер падключэнне да SIM-карты <xliff:g id="NETWORK_NAME">%1$s</xliff:g> стала больш бяспечным"</string> - <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Падключана да зашыфраванай сеткі"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Падключана да незашыфраванай сеткі"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Зараз выклікі, паведамленні і даныя менш абаронены пры выкарыстанні SIM-карты <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Зараз выклікі, паведамленні і даныя менш абаронены пры выкарыстанні SIM-карты <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nКалі падключэнне будзе зноў зашыфравана, вы атрымаеце апавяшчэнне."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Налады сеткавай бяспекі"</string> @@ -2415,4 +2415,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Адкрыць Паведамленні"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Як гэта працуе"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"У чаканні..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index 2f1ecdbbbb91..eb2e9207367f 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -161,7 +161,7 @@ <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Установена е връзка с шифрованата мрежа <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Връзката със SIM картата от <xliff:g id="NETWORK_NAME">%1$s</xliff:g> вече е по-сигурна"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Установена е връзка с нешифрована мрежа"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Понастоящем обажданията, съобщенията и данните са по-уязвими, докато използвате SIM картата си от <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Обажданията, съобщенията и данните са по-уязвими, докато използвате SIM картата си от <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Понастоящем обажданията, съобщенията и данните са по-уязвими, докато използвате SIM картата си от <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nСлед като връзката ви бъде шифрована отново, ще получите друго известие."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Настройки за сигурност на мобилната мрежа"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Научете повече"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Отваряне на Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Начин на работа"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Изчаква..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index f1ebfb02faa5..6618127ada43 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -157,7 +157,7 @@ <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"এনক্রিপশন, এনক্রিপটেড নয় এমন নেটওয়ার্কের জন্য বিজ্ঞপ্তি"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"ডিভাইস আইডি অ্যাক্সেস করা হয়েছে"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"আপনার <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> সিম কার্ড ব্যবহার করে <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>-এ, আশেপাশের নেটওয়ার্ক আপনার ডিভাইসের অনন্য আইডি (IMSI অথবা IMEI) রেকর্ড করেছে"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"আপনার <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> সিম কার্ড ব্যবহার করে <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>-এ, আশেপাশের নেটওয়ার্ক আপনার ডিভাইসের অনন্য আইডি (IMSI অথবা IMEI) রেকর্ড করেছে।\n\nএটির মানে হল আপনার লোকেশন, অ্যাক্টিভিটি বা পরিচিতি লগ-ইন করা হয়েছে। এটি সাধারণ পদ্ধতি কিন্তু সেইসব লোকজনের জন্য সমস্যা হতে পারে যারা নিজেদের গোপনীয়তা নিয়ে উদ্বেগে থাকেন।"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"আপনার <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> সিম কার্ড ব্যবহার করে <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>-এ, আশেপাশের নেটওয়ার্ক আপনার ডিভাইসের অনন্য আইডি (IMSI অথবা IMEI) রেকর্ড করেছে।\n\nএটির মানে হল আপনার লোকেশন, অ্যাক্টিভিটি বা পরিচিতি লগ করা হয়েছে। এটি সাধারণ পদ্ধতি কিন্তু সেইসব লোকজনের জন্য সমস্যা হতে পারে যারা নিজেদের গোপনীয়তা নিয়ে উদ্বেগে থাকেন।"</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> এনক্রিপটেড নেটওয়ার্কের সাথে কানেক্ট করা রয়েছে"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"এখন <xliff:g id="NETWORK_NAME">%1$s</xliff:g> সিম কার্ডের কানেকশন আরও সুরক্ষিত"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"এনক্রিপটেড নয় এমন নেটওয়ার্কের সাথে কানেক্ট করা রয়েছে"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages খুলুন"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"এটি কীভাবে কাজ করে"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"বাকি আছে…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml index 1212af9c2a24..4f058bf74b41 100644 --- a/core/res/res/values-bs/strings.xml +++ b/core/res/res/values-bs/strings.xml @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvorite Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kako ovo funkcionira"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Na čekanju…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index f2447791a65a..77fd6b833bb8 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Obre Missatges"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Com funciona"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendent..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 6b0426729a1d..5615f79d1013 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -2415,4 +2415,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otevřít Zprávy"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Jak to funguje"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Čeká na vyřízení…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index 9bd374ae12e9..fd04e42c8e1f 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Åbn Beskeder"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Sådan fungerer det"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Afventer…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 24a71b5ca45d..630ec7512784 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -156,12 +156,12 @@ <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Sicherheit des Mobilfunknetzes"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Verschlüsselung, Benachrichtigungen für unverschlüsselte Netzwerke"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Auf Geräte-ID zugegriffen"</string> - <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Um <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> hat ein Netzwerk in der Nähe die eindeutige ID (IMSI oder IMEI) deines Geräts aufgezeichnet, während du deine <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-SIM verwendet hast."</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Um <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> hat ein Netzwerk in der Nähe die eindeutige ID (IMSI oder IMEI) deines Geräts aufgezeichnet, während du deine <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-SIM verwendet hast.\n\nDaher wurden dein Standort, deine Aktivitäten oder deine Identität protokolliert. Das ist zwar üblich, kann jedoch ein Problem für Personen sein, denen ihre Privatsphäre wichtig ist."</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Um <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> hat ein Netzwerk in der Nähe die eindeutige ID (IMSI oder IMEI) deines Geräts aufgezeichnet, während du <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-SIM verwendet hast."</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Um <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> hat ein Netzwerk in der Nähe die eindeutige ID (IMSI oder IMEI) deines Geräts aufgezeichnet, während du <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-SIM verwendet hast.\n\nDaher wurden dein Standort, deine Aktivitäten oder deine Identität protokolliert. Das ist zwar üblich, kann jedoch ein Problem für Personen sein, denen ihre Privatsphäre wichtig ist."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Mit verschlüsseltem Netzwerk „<xliff:g id="NETWORK_NAME">%1$s</xliff:g>“ verbunden"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Verbindung der <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-SIM ist jetzt sicherer"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Mit unverschlüsseltem Netzwerk verbunden"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Anrufe, Nachrichten und Daten sind momentan anfälliger für Angriffe, während du deine <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-SIM verwendest"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Anrufe, Nachrichten und Daten sind anfälliger für Angriffe, während du <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-SIM verwendest"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Anrufe, Nachrichten und Daten sind momentan anfälliger für Angriffe, während du deine <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-SIM verwendest.\n\nWenn deine Verbindung wieder verschlüsselt ist, erhältst du eine weitere Benachrichtigung."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Einstellungen für die Sicherheit des Mobilfunknetzes"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Weitere Informationen"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages öffnen"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"So funktionierts"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Ausstehend…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index 32611ef20d39..54901313dad2 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Άνοιγμα Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Πώς λειτουργεί"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Σε εκκρεμότητα…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml index 1e1a6873ae4d..a32fcca8fc1f 100644 --- a/core/res/res/values-en-rAU/strings.xml +++ b/core/res/res/values-en-rAU/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml index 9951afd5137f..9f06f71c147f 100644 --- a/core/res/res/values-en-rCA/strings.xml +++ b/core/res/res/values-en-rCA/strings.xml @@ -2413,4 +2413,13 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending..."</string> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Set up Fingerprint Unlock again"</string> + <string name="fingerprint_dangling_notification_msg_1" msgid="6261149111900787302">"<xliff:g id="FINGERPRINT">%s</xliff:g> wasn\'t working well and was deleted to improve performance"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7688302770424064884">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> and <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> weren\'t working well and were deleted to improve performance"</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> wasn\'t working well and was deleted. Set it up again to unlock your phone with fingerprint."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> and <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> weren\'t working well and were deleted. Set them up again to unlock your phone with your fingerprint."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Set up Face Unlock again"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Your face model wasn\'t working well and was deleted. Set it up again to unlock your phone with face."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Set up"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Not now"</string> </resources> diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index 8d5568169d33..bfcc4be89814 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml index a8e397d5b054..80007323188c 100644 --- a/core/res/res/values-en-rIN/strings.xml +++ b/core/res/res/values-en-rIN/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml index e7f713bb8822..0fe2cccd8d58 100644 --- a/core/res/res/values-en-rXC/strings.xml +++ b/core/res/res/values-en-rXC/strings.xml @@ -2413,4 +2413,13 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending..."</string> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Set up Fingerprint Unlock again"</string> + <string name="fingerprint_dangling_notification_msg_1" msgid="6261149111900787302">"<xliff:g id="FINGERPRINT">%s</xliff:g> wasn\'t working well and was deleted to improve performance"</string> + <string name="fingerprint_dangling_notification_msg_2" msgid="7688302770424064884">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> and <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> weren\'t working well and were deleted to improve performance"</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> wasn\'t working well and was deleted. Set it up again to unlock your phone with fingerprint."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> and <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> weren\'t working well and were deleted. Set them up again to unlock your phone with your fingerprint."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Set up Face Unlock again"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Your face model wasn\'t working well and was deleted. Set it up again to unlock your phone with face."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Set up"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Not now"</string> </resources> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 5d4e95121033..8717640a6910 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -156,7 +156,7 @@ <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: no se ha remitido"</string> <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Seguridad de redes móviles"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Encriptación, notificaciones para redes no encriptadas"</string> - <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Se accedió al ID de dispositivo"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Se accedió al ID del dispositivo"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"A la(s) <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, una red cercana registró el ID único de tu dispositivo (IMSI o IMEI) mientras se usaba tu SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"A la(s) <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, una red cercana registró el ID único de tu dispositivo (IMSI o IMEI) mientras se usaba tu SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nEsto significa que se registró tu ubicación, actividad o identidad. Esta es una práctica común, pero podría significar un problema para personas preocupadas por su privacidad."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Se conectó a la red encriptada de <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir Mensajes"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cómo funciona"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendiente…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 19be81f0f4f9..6549da21376f 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -158,7 +158,7 @@ <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Cifrado, notificaciones sobre redes no cifradas"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Se ha accedido al ID del dispositivo"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"A las <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, una red cercana registró el ID único de tu dispositivo (IMSI o IMEI) mientras usabas la SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"A las <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, una red cercana registró el ID único de tu dispositivo (IMSI o IMEI) mientras usabas la SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nEsto significa que se ha registrado tu ubicación, actividad o identidad. Se trata de una práctica habitual, pero puede ser un problema para aquellos a quienes les preocupa su privacidad."</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"A las <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, una red cercana registró el ID único de tu dispositivo (IMSI o IMEI) mientras usabas la SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nEsto significa que se ha registrado tu ubicación, actividad o identidad. Se trata de una práctica habitual, pero puede ser un problema para quienes les preocupa su privacidad."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Conectado a la red cifrada <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Ahora, la conexión con la SIM de <xliff:g id="NETWORK_NAME">%1$s</xliff:g> es más segura"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Conectado a una red no cifrada"</string> @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abre Mensajes"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cómo funciona"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendiente..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index 4a17e0db6c84..92f89e3ccb99 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -154,7 +154,7 @@ <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: pole suunatud"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: pole edastatud"</string> <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Mobiilsidevõrgu turve"</string> - <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Krüpteerimine, märguanded krüpteerimata võrkude jaoks"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Krüpteerimine, märguanded krüpteerimata võrkude kohta"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Seadme ID-le on juurde pääsetud"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Kell <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> salvestas lähedal olev võrk võrgu <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM-i kasutamise ajal teie seadme kordumatu ID (IMSI või IMEI)"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Kell <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> salvestas lähedal olev võrk võrgu <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM-i kasutamise ajal teie seadme kordumatu ID (IMSI või IMEI).\n\nSee tähendab, et teie asukoht, tegevus või isik salvestati. See on levinud tava, kuid võib osutada probleemiks inimeste jaoks, kellele on privaatsus eriti oluline."</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Ava rakendus Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Tööpõhimõtted"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Ootel …"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index 3f3404c56b8e..2d4130e23fc0 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -2144,10 +2144,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Ohitura moduaren informazio-jakinarazpena"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Bateria-aurreztailea aktibatu da"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Bateria-erabilera murrizten hasi da haren iraupena luzatzeko"</string> - <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) --> - <skip /> - <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) --> - <skip /> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Bateria-aurreztailea aktibatuta dago"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Bateria-aurreztailea aktibatuta dago, bateriaren iraupena luzatzeko"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Bateria-aurreztailea"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Desaktibatu egin da bateria-aurreztailea"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Behar adina bateria dauka telefonoak. Jada ez dago eginbiderik murriztuta."</string> @@ -2415,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Ireki Mezuak"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Nola funtzionatzen du?"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Zain…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 3f850c12c9c2..07b267471157 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -155,12 +155,12 @@ <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: هدایت نشده"</string> <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"امنیت شبکه تلفن همراه"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"رمزگذاری، اعلانهای شبکههای رمزگذارینشده"</string> - <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"شناسه دستگاه دردسترس قرار گرفته است"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"شناسه دستگاه مورددسترس قرار گرفت"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"ساعت <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>، یکی از شبکههای اطراف شناسه یکتای دستگاهتان (IMSI یا IMEI) را هنگام استفاده از سیمکارت <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> ضبط کرده است"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"ساعت <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>، یکی از شبکههای اطراف شناسه یکتای دستگاهتان (IMSI یا IMEI) را هنگام استفاده از سیمکارت <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> ضبط کرده است.\n\nاین یعنی مکان، فعالیت، یا هویت شما ثبت شده است. این رویکرد عادی است اما ممکن است برای افرادی که نگران حریم خصوصیشان هستند مشکلساز باشد."</string> - <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"به شبکه رمزگذاریشده <xliff:g id="NETWORK_NAME">%1$s</xliff:g> متصل شدید"</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"به شبکه رمزگذاریشده <xliff:g id="NETWORK_NAME">%1$s</xliff:g> متصلاید"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"اتصال سیمکارت <xliff:g id="NETWORK_NAME">%1$s</xliff:g> اکنون ایمنتر است"</string> - <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"به شبکه رمزگذارینشده متصل شدید"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"به شبکه رمزگذارینشده متصلاید"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"درحالحاضر تماسها، پیامها، و دادهها هنگام استفاده از سیمکارت <xliff:g id="NETWORK_NAME">%1$s</xliff:g> آسیبپذیرتر هستند"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"درحالحاضر تماسها، پیامها، و دادهها هنگام استفاده از سیمکارت <xliff:g id="NETWORK_NAME">%1$s</xliff:g> آسیبپذیرتر هستند.\n\nوقتی اتصال شما دوباره رمزگذاری شود، اعلان دیگری دریافت خواهید کرد."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"تنظیمات امنیت شبکه تلفن همراه"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"باز کردن «پیامها»"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"روش کار"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"درحال تعلیق…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index 3e1adc44b0c3..e802443e5acb 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -160,7 +160,7 @@ <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Klo <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> lähellä oleva verkko tallensi laitteesi yksilöllisen tunnuksen (IMSI tai IMEI), kun käytössä oli SIM-kortti, jonka <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> tarjoaa.\n\nTämä tarkoittaa, että sijaintisi, toimintasi tai henkilöllisyytesi on tallennettu. Tämä on tavallista mutta voi huolestuttaa ihmisiä, jotka ovat tarkkoja yksityisyydestään."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Yhdistetty salattuun verkkoon <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"SIM-yhteys, jonka <xliff:g id="NETWORK_NAME">%1$s</xliff:g> tarjoaa, on nyt turvallisempi"</string> - <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Yhdistetty salattuun verkkoon"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Yhdistetty salaamattomaan verkkoon"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Puhelut, viestit ja data ovat haavoittuvaisempia, kun käytössä on SIM-kortti, jonka <xliff:g id="NETWORK_NAME">%1$s</xliff:g> tarjoaa"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Puhelut, viestit ja data ovat haavoittuvaisempia, kun käytössä on SIM-kortti, jonka <xliff:g id="NETWORK_NAME">%1$s</xliff:g> tarjoaa.\n\nKun yhteys on taas salattu, saat uuden ilmoituksen."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Mobiiliverkkoa koskevat turvallisuusasetukset"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Avaa Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Näin se toimii"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Odottaa…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index dd968e1ec185..bf70c3cd4e97 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -162,8 +162,8 @@ <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Connecté à un réseau chiffré <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"La connexion à la carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> est maintenant plus sûre"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Connecté à un réseau non chiffré"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Les appels, les messages et les données sont actuellement plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Les appels, les messages et les données sont actuellement plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nUne fois que votre connexion est à nouveau chiffrée, vous recevez une nouvelle notification."</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Les appels, messages et données sont plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Les appels, les messages et les données sont actuellement plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nLorsque votre connexion sera à nouveau chiffrée, vous recevrez une nouvelle notification."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Paramètres de sécurité du réseau cellulaire"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"En savoir plus"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"OK"</string> @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Ouvrir Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Fonctionnement"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"En attente…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 96636dcd3c60..57272245224f 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -163,7 +163,7 @@ <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"La connexion à la carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> est désormais plus sécurisée"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Connecté à un réseau non chiffré"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Les appels, les messages et les données sont actuellement plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Les appels, les messages et les données sont actuellement plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nLorsque votre connexion est à nouveau chiffrée, vous recevez une nouvelle notification."</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Les appels, les messages et les données sont actuellement plus vulnérables lorsque vous utilisez votre carte SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nLorsque votre connexion sera à nouveau chiffrée, vous recevrez une nouvelle notification."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Paramètres de sécurité du réseau mobile"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"En savoir plus"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"OK"</string> @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Ouvrir Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Fonctionnement"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"En attente…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index 85ad42fe8ff5..663ef9ace52d 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -158,9 +158,9 @@ <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Acceso ao código do dispositivo"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Á/s <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, unha rede próxima rexistrou o código exclusivo (IMSI ou IMEI) do teu dispositivo mentres se usaba a túa SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Á/s <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, unha rede próxima rexistrou o código exclusivo (IMSI ou IMEI) do teu dispositivo mentres se usaba a túa SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nIsto significa que se rexistrou a túa localización, actividade ou identidade. Aínda que se trata dunha práctica común, pode supoñer un problema para as persoas ás que lles preocupe a súa privacidade."</string> - <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Conexión á rede encriptada <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Conectácheste á rede encriptada <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"A conexión coa SIM de <xliff:g id="NETWORK_NAME">%1$s</xliff:g> agora é máis segura"</string> - <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Conexión a unha rede non encriptada"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Conectácheste a unha rede non encriptada"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Cando usas a SIM de <xliff:g id="NETWORK_NAME">%1$s</xliff:g>, as chamadas, mensaxes e datos son máis vulnerables"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Cando usas a SIM de <xliff:g id="NETWORK_NAME">%1$s</xliff:g>, as chamadas, mensaxes e datos son máis vulnerables.\n\nRecibirás outra notificación cando se volva encriptar a túa conexión."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Configuración de seguranza das redes de telefonía móbil"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir Mensaxes"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona?"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendente..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index 82ffb2f55c9d..dc4253750380 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ખોલો"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"તેની કામ કરવાની રીત"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"બાકી..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index 8772cdc62a11..25f6ca1cc6f6 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -154,15 +154,15 @@ <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: अग्रेषित नहीं किया गया"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: अग्रेषित नहीं किया गया"</string> <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"मोबाइल नेटवर्क की सुरक्षा"</string> - <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"उन नेटवर्क के लिए सुरक्षा से जुड़ी सूचनाएं जो सुरक्षित नहीं हैं"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"एन्क्रिप्शन, असुरक्षित नेटवर्क के लिए सूचनाएं"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"डिवाइस आईडी को ऐक्सेस किया गया"</string> - <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"आपके <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> सिम का इस्तेमाल करके, आस-पास के नेटवर्क ने <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> आपके डिवाइस का यूनीक आईडी (IMSI या IMEI) रिकॉर्ड किया"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"आपका <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> सिम इस्तेमाल करके, आस-पास के नेटवर्क ने <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> आपके डिवाइस का यूनीक आईडी (IMSI या IMEI) रिकॉर्ड किया.\n\nइसका मतलब है कि आपकी जगह की जानकारी, गतिविधि या निजी जानकारी को लॉग इन किया गया है. यह सामान्य तरीका है. हालांकि, यह उन लोगों के लिए समस्या की वजह हो सकता है जिन्हें अपनी निजी जानकारी को लेकर चिंता रहती है."</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"आपके <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> सिम के ज़रिए, आस-पास के नेटवर्क ने <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> पर आपके डिवाइस का यूनीक आईडी (IMSI या IMEI) रिकॉर्ड किया"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"आपके <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> सिम के ज़रिए, आस-पास के नेटवर्क ने <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> पर आपके डिवाइस का यूनीक आईडी (IMSI या IMEI) रिकॉर्ड किया.\n\nइसका मतलब है कि आपकी जगह की जानकारी, गतिविधि या निजी जानकारी को लॉग किया गया है. यह आम बात है. हालांकि, यह उन लोगों के लिए समस्या की वजह हो सकता है जिन्हें अपनी निजी जानकारी को लेकर चिंता रहती है."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"एन्क्रिप्ट यानी सुरक्षित नेटवर्क <xliff:g id="NETWORK_NAME">%1$s</xliff:g> से कनेक्ट किया गया"</string> - <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"अब <xliff:g id="NETWORK_NAME">%1$s</xliff:g> सिम का कनेक्शन ज़्यादा सुरक्षित है"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"अब <xliff:g id="NETWORK_NAME">%1$s</xliff:g> सिम का कनेक्शन ज़्यादा सुरक्षित हो गया है"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"ऐसे नेटवर्क से कनेक्ट किया गया जो सुरक्षित नहीं है"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> सिम का इस्तेमाल करने के दौरान, कॉल, मैसेज, और डेटा को ऐक्सेस किए जाने का खतरा हो सकता है"</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> सिम का इस्तेमाल करने के दौरान, कॉल, मैसेज, और डेटा को ऐक्सेस किए जाने का खतरा हो सकता है.\n\nजब आपका कनेक्शन फिर से एन्क्रिप्ट यानी सुरक्षित हो जाएगा, तब आपको दोबारा सूचना भेजी जाएगी."</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> सिम का इस्तेमाल करने पर, कॉल, मैसेज, और डेटा ऐक्सेस किए जाने का खतरा हो सकता है"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> सिम का इस्तेमाल करने पर, कॉल, मैसेज, और डेटा ऐक्सेस किए जाने का खतरा हो सकता है.\n\nजब आपका कनेक्शन फिर से एन्क्रिप्ट यानी सुरक्षित हो जाएगा, तब आपको नई सूचना भेजी जाएगी."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"मोबाइल नेटवर्क की सुरक्षा से जुड़ी सेटिंग"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"ज़्यादा जानें"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"ठीक है"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ऐप्लिकेशन खोलें"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"यह सेटिंग कैसे काम करती है"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"प्रोसेस जारी है..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index 88bc29fc3a3c..117d4e5632c6 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvori Poruke"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kako to funkcionira"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Na čekanju..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index f7e4a8f6ffe9..de8fa848edd6 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"A Messages megnyitása"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Hogyan működik?"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Függőben…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index 251646c15031..d37993442bcf 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -153,9 +153,9 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>. <xliff:g id="DIALING_NUMBER">{1}</xliff:g> <xliff:g id="TIME_DELAY">{2}</xliff:g> վայրկյանից"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>. Չի վերահասցեավորվել"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>. Չի վերահասցեավորվել"</string> - <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Ցանցային անվտանգություն"</string> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Բջջային ցանցի անվտանգություն"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Գաղտնագրում, ծանուցումներ չգաղտնագրված ցանցերի համար"</string> - <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Սարքի նույնացույցիչը հասանելի է դարձել"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Սարքի նույնացուցիչը հասանելի է դարձել"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Ժամը <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>-ին մոտակա ցանցը գրանցել է ձեր սարքի եզակի նույնացուցիչը (IMSI-ը կամ IMEI-ը) <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-ի ձեր SIM քարտի օգտագործման ժամանակ"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Ժամը <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>-ին մոտակա ցանցը գրանցել է ձեր սարքի եզակի նույնացուցիչը (IMSI-ը կամ IMEI-ը) <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-ի ձեր SIM քարտի օգտագործման ժամանակ։\n\nԴա նշանակում է, որ ձեր տեղադրությունը, գործողությունները կամ անձը նույնականացնող տվյալները գրանցվել են։ Սա սովորական գործելակերպ է, սակայն կարող է խնդիր լինել այն մարդկանց համար, որոնք մտահոգված են իրենց գաղտնիությամբ։"</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Հեռախոսը միացավ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> գաղտնագրված ցանցին"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Բացել Messages-ը"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ինչպես է դա աշխատում"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Առկախ է…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index 139b4ab8338b..e6634d23fd62 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -157,12 +157,12 @@ <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Enkripsi, notifikasi untuk jaringan yang tidak terenkripsi"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"ID perangkat diakses"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Pada <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, jaringan di sekitar merekam ID unik perangkat Anda (IMSI atau IMEI) saat menggunakan kartu SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> Anda"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Pada <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, jaringan di sekitar merekam ID unik perangkat Anda (IMSI atau IMEI) saat menggunakan kartu SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> Anda.\n\nHal ini berarti lokasi, aktifitas, atau identitas Anda telah dicatat dalam log. Tindakan ini adalah praktik umum tetapi dapat menjadi masalah bagi orang yang mengkhawatirkan privasi."</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Pada <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, jaringan di sekitar merekam ID unik perangkat Anda (IMSI atau IMEI) saat menggunakan kartu SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> Anda.\n\nHal ini berarti lokasi, aktivitas, atau identitas Anda telah dicatat dalam log. Tindakan ini adalah praktik umum tetapi dapat menjadi masalah bagi orang yang mengkhawatirkan privasi."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Terhubung ke jaringan yang terenkripsi <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Koneksi kartu SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> kini lebih aman"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Terhubung ke jaringan yang tidak terenkripsi"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Panggilan, pesan, dan data saat ini lebih rentan saat menggunakan kartu SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> Anda"</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Panggilan, pesan, dan data saat ini lebih rentan saat menggunakan kartu SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> Anda.\n\nKetika koneksi Anda terenkripsi lagi, Anda akan menerima notifikasi lainnya."</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Panggilan, pesan, dan data saat ini lebih rentan saat menggunakan kartu SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> Anda.\n\nKetika koneksi Anda terenkripsi lagi, Anda akan kembali menerima notifikasi."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Setelan keamanan jaringan seluler"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Pelajari lebih lanjut"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Oke"</string> @@ -639,7 +639,7 @@ <string name="permdesc_mediaLocation" msgid="597912899423578138">"Mengizinkan aplikasi untuk membaca lokasi dari koleksi media Anda."</string> <string name="biometric_app_setting_name" msgid="3339209978734534457">"Gunakan biometrik"</string> <string name="biometric_or_screen_lock_app_setting_name" msgid="5348462421758257752">"Gunakan biometrik atau kunci layar"</string> - <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verifikasi bahwa ini memang Anda"</string> + <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verifikasi diri Anda"</string> <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Gunakan biometrik untuk melanjutkan"</string> <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Gunakan biometrik atau kunci layar untuk melanjutkan"</string> <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Hardware biometrik tidak tersedia"</string> @@ -2221,7 +2221,7 @@ <string name="miniresolver_call_information" msgid="6739417525304184083">"Organisasi Anda hanya mengizinkan menelepon dari aplikasi kerja"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Organisasi Anda hanya mengizinkan pengiriman pesan dari aplikasi kerja"</string> <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Anda hanya dapat melakukan panggilan telepon dari aplikasi Telepon pribadi. Panggilan yang dilakukan dengan aplikasi Telepon pribadi akan ditambahkan ke histori panggilan pribadi."</string> - <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Anda hanya dapat mengirim pesan SMS dari aplikasi Message pribadi."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Anda hanya dapat mengirim pesan SMS dari aplikasi Pesan pribadi."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Gunakan browser pribadi"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Gunakan browser kerja"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Telepon"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Buka Message"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cara kerjanya"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Tertunda..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml index 1f55241f1363..f77a18a3911b 100644 --- a/core/res/res/values-is/strings.xml +++ b/core/res/res/values-is/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Opna Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Svona virkar þetta"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Í bið…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 2f066b5f46cb..95ccad54d63f 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Apri Messaggi"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Come funziona"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"In attesa…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index 3fc962acc9a7..fee437af96cf 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -162,9 +162,9 @@ <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"יש חיבור לרשת המוצפנת <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"החיבור של כרטיס ה-SIM של <xliff:g id="NETWORK_NAME">%1$s</xliff:g> מאובטח יותר עכשיו"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"יש חיבור לרשת לא מוצפנת"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"השיחות, ההודעות והנתונים שלך פגיעים יותר עכשיו בזמן השימוש בכרטיס ה-SIM של <xliff:g id="NETWORK_NAME">%1$s</xliff:g> שלך"</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"שיחות, הודעות ונתונים פגיעים יותר עכשיו בזמן השימוש בכרטיס ה-SIM של <xliff:g id="NETWORK_NAME">%1$s</xliff:g> שלך.\n\nכשהחיבור שלך יוצפן שוב, תישלח אליך התראה נוספת."</string> - <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"הגדרות אבטחה של רשת סלולרית"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"השיחות, ההודעות והנתונים שלך פגיעים יותר עכשיו בזמן השימוש בכרטיס ה-SIM של <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"השיחות, ההודעות והנתונים שלך פגיעים יותר עכשיו בזמן השימוש בכרטיס ה-SIM של <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nכשהחיבור שלך יוצפן שוב, תישלח אליך התראה נוספת."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"הגדרות אבטחה של הרשת הסלולרית"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"מידע נוסף"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"הבנתי"</string> <string name="fcComplete" msgid="1080909484660507044">"קוד תכונה הושלם."</string> @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"לפתיחת Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"איך זה עובד"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"בהמתנה..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index db3ba5a49246..726db1c4ea89 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -154,7 +154,7 @@ <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:転送できません"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:転送できません"</string> <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"モバイル ネットワーク セキュリティ"</string> - <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"暗号化(ネットワークが暗号化されていない場合に通知)"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"暗号化、暗号化されていないネットワークに関する通知"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"デバイス ID へのアクセスが発生しました"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>、<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> の SIM の使用中に付近のネットワークでお使いのデバイスの一意の ID(IMSI または IMEI)が記録されました"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>、<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> の SIM の使用中に付近のネットワークでお使いのデバイスの一意の ID(IMSI または IMEI)が記録されました。\n\nつまり、あなたの位置情報、アクティビティ、身元などが記録されことになります。これはよくある事象ですが、プライバシーに不安を持たれている人にとっては問題になる可能性があります。"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"メッセージ アプリを開く"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"仕組み"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"保留中..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml index 97003c4fd11b..bdf6c489326b 100644 --- a/core/res/res/values-ka/strings.xml +++ b/core/res/res/values-ka/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages-ის გახსნა"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"მუშაობის პრინციპი"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"მომლოდინე..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index 116166d5c3c9..55cf92bb3280 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -162,7 +162,7 @@ <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Енді <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасымен қосылу қорғалған."</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Шифрланбаған желіге қосылды"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасын пайдаланған кезде, қазіргі уақытта қоңырауларға, хабарларға және деректерге қауіп төнеді."</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасын пайдаланған кезде, қазіргі уақытта қоңырауларға, хабарларға және деректерге қауіп төнеді.\n\nБайланыс қайта шифрланғанда, тағы бір хабарландыру келеді."</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасын пайдаланған кезде, қазір қоңырауларға, хабарларға және деректерге зиян тию қаупі жоғары.\n\nБайланыс қайта шифрланғанда, тағы бір хабарландыру келеді."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Мобильдік желінің қауіпсіздік параметрлері"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Толық ақпарат"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Түсінікті"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages қолданбасын ашу"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Бұл қалай орындалады?"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Дайын емес…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml index 456256c1432c..6a826db47006 100644 --- a/core/res/res/values-km/strings.xml +++ b/core/res/res/values-km/strings.xml @@ -156,8 +156,8 @@ <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"សុវត្ថិភាពបណ្ដាញទូរសព្ទចល័ត"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"ការអ៊ីនគ្រីប ការជូនដំណឹងសម្រាប់បណ្ដាញដែលមិនបានអ៊ីនគ្រីប"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"បានចូលប្រើប្រាស់លេខសម្គាល់ឧបករណ៍"</string> - <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"នៅម៉ោង <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> បណ្ដាញនៅជិតបានកត់ត្រាលេខសម្គាល់ពិសេស (IMSI ឬ IMEI) របស់ឧបករណ៍អ្នក ពេលកំពុងប្រើស៊ីម <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> របស់អ្នក"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"នៅម៉ោង <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> បណ្ដាញនៅជិតបានកត់ត្រាលេខសម្គាល់ពិសេស (IMSI ឬ IMEI) របស់ឧបករណ៍អ្នក ពេលកំពុងប្រើស៊ីម <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> របស់អ្នក។\n\nនេះមានន័យថា ទីតាំង សកម្មភាព ឬអត្តសញ្ញាណរបស់អ្នកត្រូវបានចុះកំណត់ហេតុ។ នេះគឺជាការអនុវត្តទូទៅ ប៉ុន្តែអាចនឹងមានបញ្ហាសម្រាប់អ្នកដែលមានកង្វល់ពាក់ព័ន្ធនឹងឯកជនភាព។"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"នៅម៉ោង <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> បណ្ដាញនៅជិតបានកត់ត្រាលេខកូដសម្គាល់ខុសពីគេ (IMSI ឬ IMEI) របស់ឧបករណ៍អ្នក ពេលកំពុងប្រើស៊ីម <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> របស់អ្នក"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"នៅម៉ោង <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> បណ្ដាញនៅជិតបានកត់ត្រាលេខកូដសម្គាល់ខុសពីគេ (IMSI ឬ IMEI) របស់ឧបករណ៍អ្នក ពេលកំពុងប្រើស៊ីម <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> របស់អ្នក។\n\nនេះមានន័យថា ទីតាំង សកម្មភាព ឬអត្តសញ្ញាណរបស់អ្នកត្រូវបានកត់ត្រាទុក។ នេះគឺជាការអនុវត្តទូទៅ ប៉ុន្តែអាចនឹងមានបញ្ហាសម្រាប់អ្នកដែលមានកង្វល់ពាក់ព័ន្ធនឹងឯកជនភាព។"</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"បានភ្ជាប់ទៅបណ្ដាញដែលបានអ៊ីនគ្រីប <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"ការតភ្ជាប់ស៊ីម <xliff:g id="NETWORK_NAME">%1$s</xliff:g> កាន់តែមានសុវត្ថិភាពឥឡូវនេះ"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"បានភ្ជាប់ទៅបណ្ដាញដែលមិនបានអ៊ីនគ្រីប"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"បើកកម្មវិធី Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"របៀបដែលវាដំណើរការ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"កំពុងរង់ចាំ..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index 97f013638877..40d513893dda 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -156,12 +156,12 @@ <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"ಮೊಬೈಲ್ ನೆಟ್ವರ್ಕ್ ಸೆಕ್ಯೂರಿಟಿ"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"ಎನ್ಕ್ರಿಪ್ಶನ್, ಎನ್ಕ್ರಿಪ್ಟ್ ಮಾಡದ ನೆಟ್ವರ್ಕ್ಗಳಿಗೆ ನೋಟಿಫಿಕೇಶನ್ಗಳು"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"ಸಾಧನದ ID ಅನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲಾಗಿದೆ"</string> - <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> ಸಮಯದಲ್ಲಿ, ನಿಮ್ಮ ಸಾಧನದ ಅನನ್ಯ ID (IMSI ಅಥವಾ IMEI) ಅನ್ನು ನಿಮ್ಮ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM ಕಾರ್ಡ್ ಬಳಸುವಾಗ ಹತ್ತಿರದ ನೆಟ್ವರ್ಕ್ನಿಂದ ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗಿದೆ"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>ಕ್ಕೆ, <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM ಬಳಸುವಾಗ ಸಾಧನದ ಅನನ್ಯ ID (IMSI ಅಥವಾ IMEI)ಯನ್ನು ಹತ್ತಿರದ ನೆಟ್ವರ್ಕ್ ರೆಕಾರ್ಡ್ ಮಾಡಿದೆ"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> ಸಮಯದಲ್ಲಿ, ನಿಮ್ಮ ಸಾಧನದ ಅನನ್ಯ ID (IMSI ಅಥವಾ IMEI) ಅನ್ನು ನಿಮ್ಮ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM ಕಾರ್ಡ್ ಬಳಸುವಾಗ ಹತ್ತಿರದ ನೆಟ್ವರ್ಕ್ನಿಂದ ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗಿದೆ.\n\nಇದರರ್ಥ ನಿಮ್ಮ ಸ್ಥಳ, ಚಟುವಟಿಕೆ ಅಥವಾ ಗುರುತನ್ನು ಲಾಗ್ ಮಾಡಲಾಗಿದೆ. ಇದು ಸಾಮಾನ್ಯ ವಿಧಾನವಾಗಿದೆ ಆದರೆ ಗೌಪ್ಯತೆಯ ಕುರಿತು ಕಾಳಜಿವಹಿಸುವವರಿಗೆ ಸಮಸ್ಯೆಯಾಗಬಹುದು."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"ಎನ್ಕ್ರಿಪ್ಟ್ ಮಾಡಿದ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ನೆಟ್ವರ್ಕ್ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM ಕನೆಕ್ಷನ್ ಈಗ ಹೆಚ್ಚು ಸುರಕ್ಷಿತವಾಗಿದೆ"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"ಎನ್ಕ್ರಿಪ್ಟ್ ಮಾಡದ ನೆಟ್ವರ್ಕ್ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"ನಿಮ್ಮ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM ಕಾರ್ಡ್ ಬಳಸುವಾಗ ಕರೆಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಡೇಟಾ ಪ್ರಸ್ತುತ ಹೆಚ್ಚು ಸೂಕ್ಷ್ಮವಾಗಿರುತ್ತದೆ."</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"ನಿಮ್ಮ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM ಕಾರ್ಡ್ ಬಳಸುವಾಗ ಕರೆಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಡೇಟಾವನ್ನು ಬೇರೆಯವರು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಬಹುದಾದ ಅಪಾಯವಿರುತ್ತದೆ"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"ನಿಮ್ಮ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM ಕಾರ್ಡ್ ಬಳಸುವಾಗ ಕರೆಗಳು, ಸಂದೇಶಗಳು ಮತ್ತು ಡೇಟಾ ಪ್ರಸ್ತುತ ಹೆಚ್ಚು ಸೂಕ್ಷ್ಮವಾಗಿರುತ್ತದೆ.\n\nನಿಮ್ಮ ಕನೆಕ್ಷನ್ ಅನ್ನು ಮತ್ತೆ ಎನ್ಕ್ರಿಪ್ಟ್ ಮಾಡಿದಾಗ, ನೀವು ಇನ್ನೊಂದು ನೋಟಿಫಿಕೇಶನ್ ಅನ್ನು ಪಡೆಯುತ್ತೀರಿ."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"ಮೊಬೈಲ್ ನೆಟ್ವರ್ಕ್ ಸೆಕ್ಯೂರಿಟಿ ಸೆಟ್ಟಿಂಗ್ಗಳು"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ಅನ್ನು ತೆರೆಯಿರಿ"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ಇದು ಹೇಗೆ ಕೆಲಸ ಮಾಡುತ್ತದೆ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"ಬಾಕಿ ಉಳಿದಿದೆ..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index df29128d17ed..dd476281eec9 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"메시지 열기"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"작동 방식"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"대기 중…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml index 9511db4fd9d3..bb29c36a0410 100644 --- a/core/res/res/values-ky/strings.xml +++ b/core/res/res/values-ky/strings.xml @@ -156,13 +156,13 @@ <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Мобилдик тармактын коопсуздугу"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Шифрлөө, шифрленбеген тармактар жөнүндө билдирмелер"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Түзмөктүн идентификатору колдонулду"</string> - <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM картасын колдонуп жатканда жакын жердеги тармакта түзмөгүңүздүн өзгөчө идентификатору (IMSI же IMEI) жазылды"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM картасын колдонуп жатканда жакын жердеги тармакта түзмөгүңүздүн өзгөчө идентификатору (IMSI же IMEI) жазылды.\n\nЖүргөн жериңиз, аракеттериңиз же өздүгүңүз тууралуу маалымат катталды. Бул адаттагы көрүнүш болсо да, купуялыгы жөнүндө тынчсызданган адамдарга маселе жаратышы мүмкүн."</string> - <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Шифрленген тармакка (<xliff:g id="NETWORK_NAME">%1$s</xliff:g>) туташты"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Саат <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM картасы колдонулуп жатканда, жакын жердеги тармак түзмөгүңүздүн өзгөчө идентификаторун (IMSI же IMEI) жазып алды."</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Саат <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM картасы колдонулуп жатканда, жакын жердеги тармак түзмөгүңүздүн өзгөчө идентификаторун (IMSI же IMEI) жазып алды.\n\nТактап айтканда, жүргөн жериңиз, аракеттериңиз же өздүгүңүз катталды. Бул адаттагы көрүнүш болсо да, купуялыгы жөнүндө тынчсызданган адамдарга маселе жаратышы мүмкүн."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Шифрленген <xliff:g id="NETWORK_NAME">%1$s</xliff:g> тармагына туташты"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Эми <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасы менен туташуу коопсуз болуп калды"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Шифрленбеген тармакка туташты"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Учурда <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасын колдонсоңуз, чалууларга, билдирүүлөргө жана маалыматтарга коркунуч жаралышы мүмкүн"</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Учурда <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасын колдонсоңуз, чалууларга, билдирүүлөргө жана маалыматтарга коркунуч жаралышы мүмкүн.\n\nТуташууңуз кайра шифрленгенде дагы бир билдирме аласыз."</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Учурда <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM картасын колдонсоңуз, чалууларга, билдирүүлөргө жана маалыматтарга коркунуч жаралышы мүмкүн.\n\nБайланышыңыз кайра шифрленгенде дагы бир билдирме аласыз."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Мобилдик тармактын коопсуздук параметрлери"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Кеңири маалымат"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Түшүндүм"</string> @@ -1334,7 +1334,7 @@ <!-- no translation found for network_available_sign_in_detailed (7520423801613396556) --> <skip /> <string name="wifi_no_internet" msgid="1386911698276448061">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> Интернетке туташуусу жок"</string> - <string name="wifi_no_internet_detailed" msgid="634938444133558942">"Параметрлерди ачуу үчүн таптап коюңуз"</string> + <string name="wifi_no_internet_detailed" msgid="634938444133558942">"Параметрлерди ачуу үчүн тийип коюңуз"</string> <string name="mobile_no_internet" msgid="4014455157529909781">"Мобилдик Интернет жок"</string> <string name="other_networks_no_internet" msgid="6698711684200067033">"Тармактын Интернет жок"</string> <string name="private_dns_broken_detailed" msgid="3709388271074611847">"Жеке DNS сервери жеткиликсиз"</string> @@ -1396,7 +1396,7 @@ <string name="usb_midi_notification_title" msgid="7404506788950595557">"USB аркылуу MIDI режими күйгүзүлдү"</string> <string name="usb_uvc_notification_title" msgid="2030032862673400008">"Түзмөк веб-камера катары туташты"</string> <string name="usb_accessory_notification_title" msgid="1385394660861956980">"USB шайманы туташты"</string> - <string name="usb_notification_message" msgid="4715163067192110676">"Кошумча параметрлерди ачуу үчүн таптап коюңуз."</string> + <string name="usb_notification_message" msgid="4715163067192110676">"Кошумча параметрлерди ачуу үчүн тийип коюңуз."</string> <string name="usb_power_notification_message" msgid="7284765627437897702">"Туташкан түзмөк кубатталууда. Дагы параметрлерди көрүү үчүн таптап коюңуз."</string> <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Аналогдук аудио жабдуу табылды"</string> <string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"Тиркелген түзмөк бул телефонго шайкеш келбейт. Көбүрөөк маалымат алуу үчүн таптап коюңуз."</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Жазышуулар колдонмосун ачуу"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ал кантип иштейт"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Кезекте турат..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml index a0aded4e183d..5fa0fc135612 100644 --- a/core/res/res/values-lo/strings.xml +++ b/core/res/res/values-lo/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"ເປີດ Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ມັນເຮັດວຽກແນວໃດ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"ລໍຖ້າດຳເນີນການ..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index 837bff76ecf2..e06c2efe31b8 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -2415,4 +2415,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Atidaryti programą „Messages“"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kaip tai veikia"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Laukiama..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index 4e244734f340..d2a6cd9af5a6 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Atvērt lietotni Ziņojumi"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Darbības principi"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Gaida…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml index 3790be062ec2..796f9efe1bbc 100644 --- a/core/res/res/values-mk/strings.xml +++ b/core/res/res/values-mk/strings.xml @@ -153,7 +153,7 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> по <xliff:g id="TIME_DELAY">{2}</xliff:g> секунди"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не е препратено"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: не е проследен"</string> - <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Обезбедување на мобилна мрежа"</string> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Безбедност на мобилна мрежа"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Шифрирање, известувања за нешифрирани мрежи"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Пристапено е до ID на уредот"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Во <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, мрежа во близина го сними уникатниот ID (IMSI или IMEI) на вашиот телефон со користење на вашата SIM-картичка на <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> @@ -161,9 +161,9 @@ <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Поврзано со шифрирана мрежа <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Врската со SIM-картичката на <xliff:g id="NETWORK_NAME">%1$s</xliff:g> сега е побезбедна"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Поврзано со нешифрирана мрежа"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Повиците, пораките и податоците во моментов се почувствителни додека ја користите вашата SIM-картичка на <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Повиците, пораките и податоците се почувствителни кога го користите вашиот SIM од <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Повиците, пораките и податоците во моментов се почувствителни додека ја користите вашата SIM-картичка на <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nКога вашата врска ќе биде шифрирана повторно, ќе добиете уште едно известување."</string> - <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Поставки за обезбедување на мобилна мрежа"</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Поставки за безбедност на мобилната мрежа"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Дознајте повеќе"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Сфатив"</string> <string name="fcComplete" msgid="1080909484660507044">"Кодот за карактеристиката заврши."</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Отворете ја Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Дознајте како функционира"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Во фаза на чекање…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml index 563efdb47efd..a0b4c8d80e96 100644 --- a/core/res/res/values-ml/strings.xml +++ b/core/res/res/values-ml/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages തുറക്കുക"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ഇത് പ്രവർത്തിക്കുന്നത് എങ്ങനെയാണ്"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"തീർപ്പാക്കിയിട്ടില്ല..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml index afde064d48f9..5ba16fe87ffd 100644 --- a/core/res/res/values-mn/strings.xml +++ b/core/res/res/values-mn/strings.xml @@ -1762,7 +1762,7 @@ <string name="user_switched" msgid="7249833311585228097">"Одоогийн хэрэглэгч <xliff:g id="NAME">%1$s</xliff:g>."</string> <string name="user_switching_message" msgid="1912993630661332336">"<xliff:g id="NAME">%1$s</xliff:g> руу сэлгэж байна…"</string> <string name="user_logging_out_message" msgid="7216437629179710359">"<xliff:g id="NAME">%1$s</xliff:g>-с гарч байна…"</string> - <string name="owner_name" msgid="8713560351570795743">"Эзэмшигч"</string> + <string name="owner_name" msgid="8713560351570795743">"Өмчлөгч"</string> <string name="guest_name" msgid="8502103277839834324">"Зочин"</string> <string name="error_message_title" msgid="4082495589294631966">"Алдаа"</string> <string name="error_message_change_not_allowed" msgid="843159705042381454">"Энэ өөрчлөлтийг админ зөвшөөрөөгүй байна"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Мессежийг нээх"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Энэ хэрхэн ажилладаг вэ?"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Хүлээгдэж буй..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index 8e8d354a3a5b..6c19cc592880 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages उघडा"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ते कसे काम करते"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"प्रलंबित आहे..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index fe4ab56b7975..82c1a5974c62 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -157,7 +157,7 @@ <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Penyulitan, pemberitahuan untuk rangkaian yang tidak disulitkan"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"ID peranti diakses"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Pada <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, rangkaian berdekatan merekodkan ID unik peranti anda (IMSI atau IMEI) semasa menggunakan SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> anda"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Pada <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, rangkaian berdekatan merekodkan ID unik peranti anda (IMSI atau IMEI) semasa menggunakan SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> anda.\n\nHal ini bermaksud bahawa lokasi, aktiviti atau identiti anda telah dilog. Amalan ini biasa dilakukan tetapi mungkin sukar dilakukan oleh pengguna yang bimbang tentang privasi."</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Pada <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, rangkaian berdekatan merekodkan ID unik peranti anda (IMSI atau IMEI) semasa menggunakan SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> anda.\n\nHal ini bermaksud bahawa lokasi, aktiviti atau identiti anda telah dilog. Amalan ini biasa dilakukan tetapi mungkin menjadi hal kepada pengguna yang bimbang tentang privasi."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Disambungkan kepada rangkaian <xliff:g id="NETWORK_NAME">%1$s</xliff:g> yang disulitkan"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Sambungan SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> kini lebih selamat"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Disambungkan kepada rangkaian yang tidak disulitkan"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Buka Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cara ciri ini berfungsi"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Belum selesai..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index 5f5b0ab61661..43b7ffafd093 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -157,12 +157,12 @@ <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"အသွင်ဝှက်ခြင်း၊ အသွင်ဝှက်မထားသော ကွန်ရက်များအတွက် အကြောင်းကြားချက်များ"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"စက် ID သုံးထားသည်"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> တွင် သင့် <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> ဆင်းမ်ကတ်ကို သုံးနေစဉ် အနီးရှိ ကွန်ရက်သည် စက်ပစ္စည်း၏ သီးသန့် ID (IMSI (သို့) IMEI) ကို မှတ်တမ်းတင်ထားသည်"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> တွင် သင့် <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> ဆင်းမ်ကတ်ကို သုံးနေစဉ် အနီးရှိ ကွန်ရက်သည် စက်ပစ္စည်း၏ သီးသန့် ID (IMSI (သို့) IMEI) ကို မှတ်တမ်းတင်ထားသည်။\n\nဆိုလိုသည်မှာ သင့်တည်နေရာ၊ လုပ်ဆောင်ချက် (သို့) အထောက်အထားကို မှတ်တမ်းတင်ထားသည်။ ၎င်းသည် သာမန်လုပ်ဆောင်မှုဖြစ်သော်လည်း ကိုယ်ရေးအချက်အလက်လုံခြုံမှုနှင့်ပတ်သက်၍ စိုးရိမ်သောသူများအတွက် ပြဿနာတစ်ခု ဖြစ်နိုင်သည်။"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> တွင် သင့် <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> ဆင်းမ်ကတ်ကို သုံးနေစဉ် အနီးရှိ ကွန်ရက်သည် စက်ပစ္စည်း၏ သီးသန့် ID (IMSI (သို့) IMEI) ကို မှတ်တမ်းတင်ထားသည်။\n\nဆိုလိုသည်မှာ သင့်တည်နေရာ၊ လုပ်ဆောင်ချက် (သို့) အထောက်အထားကို မှတ်တမ်းတင်ထားသည်။ ၎င်းသည် သာမန်လုပ်ဆောင်မှုဖြစ်သော်လည်း ကိုယ်ရေးအချက်အလက်လုံခြုံမှုနှင့် ပတ်သက်၍ အချို့သူများအတွက် စိုးရိမ်စရာ ဖြစ်နိုင်သည်။"</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"အသွင်ဝှက်ထားသော ကွန်ရက် <xliff:g id="NETWORK_NAME">%1$s</xliff:g> သို့ ချိတ်ဆက်ထားသည်"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> ဆင်းမ်ကတ် ချိတ်ဆက်မှုသည် ယခု ပိုမိုလုံခြုံပါပြီ"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"အသွင်ဝှက်မထားသော ကွန်ရက်သို့ ချိတ်ဆက်ထားသည်"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"သင့် <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ဆင်းမ်ကတ်ကို သုံးနေစဉ် ဖုန်းခေါ်ဆိုမှု၊ မက်ဆေ့ဂျ်နှင့် ဒေတာများသည် လက်ရှိတွင် ပိုမိုထိခိုက်လွယ်သည်"</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"သင့် <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ဆင်းမ်ကတ်ကို သုံးနေစဉ် ဖုန်းခေါ်ဆိုမှု၊ မက်ဆေ့ဂျ်နှင့် ဒေတာများသည် လက်ရှိတွင် ပိုမိုထိခိုက်လွယ်သည်။\n\nသင့်ချိတ်ဆက်မှုကို ထပ်မံအသွင်ဝှက်လိုက်သောအခါ အကြောင်းကြားချက်နောက်တစ်ခု ရရှိပါမည်။"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"သင့် <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ဆင်းမ် သုံးချိန်တွင် ဖုန်း၊ မက်ဆေ့ဂျ်၊ ဒေတာ လုံခြုံမှုအားနည်းသည်"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"သင့် <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ဆင်းမ် သုံးချိန်တွင် ဖုန်း၊ မက်ဆေ့ဂျ်၊ ဒေတာ လုံခြုံမှုအားနည်းသည်။\n\nသင့်ချိတ်ဆက်မှုကို ထပ်မံအသွင်ဝှက်ပါက အကြောင်းကြားချက်နောက်တစ်ခု ရရှိပါမည်။"</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"မိုဘိုင်းကွန်ရက် လုံခြုံရေး ဆက်တင်များ"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"ပိုမိုလေ့လာရန်"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"နားလည်ပြီ"</string> @@ -644,7 +644,7 @@ <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"ရှေ့ဆက်ရန် သင်၏ ဇီဝမက်ထရစ်အချက်အလက် (သို့) ဖန်သားပြင်လော့ခ်ကို သုံးပါ"</string> <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"ဇီဝအချက်အလက်သုံး ကွန်ပျူတာစက်ပစ္စည်း မရရှိနိုင်ပါ"</string> <string name="biometric_error_user_canceled" msgid="6732303949695293730">"အထောက်အထားစိစစ်ခြင်းကို ပယ်ဖျက်လိုက်သည်"</string> - <string name="biometric_not_recognized" msgid="5106687642694635888">"မသိ"</string> + <string name="biometric_not_recognized" msgid="5106687642694635888">"မသိပါ"</string> <string name="biometric_face_not_recognized" msgid="5535599455744525200">"မျက်နှာကို မသိရှိပါ"</string> <string name="biometric_error_canceled" msgid="8266582404844179778">"အထောက်အထားစိစစ်ခြင်းကို ပယ်ဖျက်လိုက်သည်"</string> <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"ပင်နံပါတ်၊ လော့ခ်ပုံစံ သို့မဟုတ် စကားဝှက် သတ်မှတ်မထားပါ"</string> @@ -2220,7 +2220,7 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"အလုပ်သုံးအက်ပ်သို့ ပြောင်းမလား။"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"သင့်အဖွဲ့အစည်းသည် သင့်အား အလုပ်သုံးအက်ပ်များမှသာ ဖုန်းဆက်ခွင့်ပြုသည်"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"သင့်အဖွဲ့အစည်းသည် သင့်အား အလုပ်သုံးအက်ပ်များမှသာ မက်ဆေ့ဂျ်ပို့ခွင့်ပြုသည်"</string> - <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"သင့်ကိုယ်ပိုင် ‘ဖုန်းအက်ပ်’ မှသာ ဖုန်းခေါ်ဆိုနိုင်သည်။ ကိုယ်ပိုင် ‘ဖုန်း’ ဖြင့် ပြုလုပ်သော ခေါ်ဆိုမှုများကို သင်၏ကိုယ်ပိုင် ခေါ်ဆိုမှုမှတ်တမ်းသို့ ထည့်ပါမည်။"</string> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"သင့်ကိုယ်ပိုင် ‘ဖုန်းအက်ပ်’ မှသာ ဖုန်းခေါ်ဆိုနိုင်သည်။ ကိုယ်ပိုင် ‘ဖုန်း’ ဖြင့် ခေါ်ဆိုမှုများကို သင်၏ ကိုယ်ပိုင် ခေါ်ဆိုမှုမှတ်တမ်းသို့ ထည့်ပါမည်။"</string> <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"သင့်ကိုယ်ပိုင် Messages အက်ပ်မှသာ SMS မက်ဆေ့ဂျ်များကို ပို့နိုင်သည်။"</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"ကိုယ်ပိုင်ဘရောင်ဇာ သုံးရန်"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"အလုပ်သုံးဘရောင်ဇာ သုံးရန်"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ဖွင့်ရန်"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"အလုပ်လုပ်ပုံ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"ဆိုင်းငံ့ထားသည်…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index c4c705c5a942..ad4ffc202466 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -156,7 +156,7 @@ <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Sikkerhet for mobilnettverk"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Kryptering, varsler for ukrypterte nettverk"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Enhets-ID-en er lest"</string> - <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> registrerte et nettverk i nærheten den unike ID-en (IMSI eller IMEI) til enheten din mens du brukte SIM-kortet fra <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> registrerte et nettverk i nærheten enhetens unike ID (IMSI eller IMEI) mens du brukte <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-SIM-kortet"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> registrerte et nettverk i nærheten den unike ID-en (IMSI eller IMEI) til enheten din mens du brukte SIM-kortet fra <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nDette betyr at posisjonen, aktiviteten eller identiteten din er registrert. Dette er en vanlig praksis, men kan være et problem for folk som er opptatt av personvern."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Koblet til det krypterte nettverket <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Tilkoblingen til SIM-kortet fra <xliff:g id="NETWORK_NAME">%1$s</xliff:g> er sikrere nå"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Åpne Meldinger"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Slik fungerer det"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Venter …"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index 559f07a3e6da..1cde247403c3 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -156,8 +156,8 @@ <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"मोबाइल नेटवर्कको सुरक्षा"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"इन्क्रिप्सन, इन्क्रिप्ट नगरिएका नेटवर्कसम्बन्धी सूचना"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"डिभाइसको ID एक्सेस गरियो"</string> - <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"तपाईंले आफ्नो <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM प्रयोग गर्दै गर्दा तपाईंको डिभाइसको नजिकै रहेको एउटा नेटवर्कले <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> बजे तपाईंको डिभाइसको अद्वितीय ID रेकर्ड गरेको छ"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"तपाईंले आफ्नो <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM प्रयोग गर्दै गर्दा तपाईंको डिभाइसको नजिकै रहेको एउटा नेटवर्कले <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> बजे तपाईंको डिभाइसको अद्वितीय ID रेकर्ड गरेको छ।\n\nयसको अर्थ तपाईंको लोकेसन, गतिविधि वा पहिचान लग गरिएको छ। यसो गर्नु सामान्य हो तर आफ्नो गोपनीयताका बारेमा चिन्ता लिने मान्छेहरूका लागि यो समस्याको विषय हुन सक्छ।"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"तपाईंले आफ्नो <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM प्रयोग गर्दै गर्दा तपाईंको डिभाइसको नजिकै रहेको एउटा नेटवर्कले <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> मा तपाईंको डिभाइसको अद्वितीय ID रेकर्ड गरेको छ"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"तपाईंले आफ्नो <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM प्रयोग गर्दै गर्दा तपाईंको डिभाइसको नजिकै रहेको एउटा नेटवर्कले <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> मा तपाईंको डिभाइसको अद्वितीय ID रेकर्ड गरेको छ।\n\nयसको अर्थ तपाईंको लोकेसन, गतिविधि वा पहिचान लग गरिएको छ। यसो गर्नु सामान्य हो तर आफ्नो गोपनीयताका बारेमा चिन्ता लिने मान्छेहरूका लागि यो समस्याको विषय हुन सक्छ।"</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"इन्क्रिप्ट गरिएको नेटवर्क <xliff:g id="NETWORK_NAME">%1$s</xliff:g> मा कनेक्ट गरियो"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM को कनेक्सन अब सुरक्षित छ"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"इन्क्रिप्ट नगरिएको नेटवर्कमा कनेक्ट गरियो"</string> @@ -466,11 +466,11 @@ <string name="permdesc_broadcastSticky" product="tablet" msgid="5058486069846384013">"औपचारिक प्रसारणलाई पठाउनको लागि एउटा एपलाई अनुमति दिन्छ, जुन प्रसारण समाप्त भएपछि बाँकी रहन्छ। अत्यधिक प्रयोगले धेरै मेमोरी प्रयोग गरेको कारणले ट्याब्लेटलाई ढिलो र अस्थिर बनाउन सक्छ।"</string> <string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"एपलाई प्रसारण समाप्त भइसकेपछि पनि रहिरहने स्टिकी प्रसारणहरू पठाउने अनुमति दिन्छ। यो सुविधाको अत्यधिक प्रयोगले धेरै मेमोरी प्रयोग हुने भएकाले तपाईंको Android टिभी यन्त्र सुस्त वा अस्थिर हुन सक्छ।"</string> <string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"औपचारिक प्रसारणलाई पठाउनको लागि एक एपलाई अनुमति दिन्छ, जुन प्रसारण समाप्त भएपछि बाँकी रहन्छ। अत्यधिक प्रयोगले धेरै मेमोरी प्रयोग गरेको कारणले फोनलाई ढिलो र अस्थिर बनाउन सक्छ।"</string> - <string name="permlab_readContacts" msgid="8776395111787429099">"तपाईँका सम्पर्कहरू पढ्नुहोस्"</string> + <string name="permlab_readContacts" msgid="8776395111787429099">"तपाईँका कन्ट्याक्टहरू पढ्नुहोस्"</string> <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"एपलाई तपाईंको ट्याब्लेटमा भण्डारण गरिएका कन्ट्याक्टहरूसँग सम्बन्धित डेटा पढ्ने अनुमति दिन्छ। एपहरूले कन्ट्याक्टहरू बनाउने तपाईंको ट्याब्लेटमा भण्डारण गरिएका खाताहरूमाथि पनि पहुँच प्राप्त गर्ने छन्। यसमा तपाईंले स्थापना गरेका एपहरूले बनाएका खाताहरू पर्न सक्छन्। यस अनुमतिले एपहरूलाई तपाईंको सम्पर्क ठेगानासम्बन्धी डेटा सेभ गर्न दिने भएकाले हानिकारक एपहरूले तपाईंलाई थाहै नदिइकन सम्पर्क ठेगानासम्बन्धी डेटा आदान प्रदान गर्न सक्छन्।"</string> <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"एपलाई तपाईंको Android टिभी डिभाइसमा भण्डारण गरिएका सम्पर्क ठेगानासम्बन्धी डेटा पढ्न अनुमति दिन्छ। एपहरूले कन्ट्याक्टहरू बनाउने तपाईंको Android टिभी डिभाइसमा भण्डारण गरिएका खाताहरूमाथि पनि पहुँच प्राप्त गर्ने छन्। यसमा तपाईंले स्थापना गरेका एपहरूले बनाएका खाताहरू पर्न सक्छन्। यस अनुमतिले एपहरूलाई तपाईंको सम्पर्क ठेगानासम्बन्धी डेटा सेभ गर्न दिने भएकाले हानिकारक एपहरूले तपाईंलाई थाहै नदिइकन सम्पर्क ठेगानासम्बन्धी डेटा आदान प्रदान गर्न सक्छन्।"</string> <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"एपलाई तपाईंको फोनमा भण्डारण गरिएका कन्ट्याक्टहरूसँग सम्बन्धित डेटा पढ्ने अनुमति दिन्छ। एपहरूले कन्ट्याक्टहरू बनाउने तपाईंको फोनमा भण्डारण गरिएका खाताहरूमाथि पनि पहुँच प्राप्त गर्ने छन्। यसमा तपाईंले स्थापना गरेका एपहरूले बनाएका खाताहरू पर्न सक्छन्। यस अनुमतिले एपहरूलाई तपाईंको सम्पर्क ठेगानासम्बन्धी डेटा सेभ गर्न दिने भएकाले हानिकारक एपहरूले तपाईंलाई थाहै नदिइकन सम्पर्क ठेगानासम्बन्धी डेटा आदान प्रदान गर्न सक्छन्।"</string> - <string name="permlab_writeContacts" msgid="8919430536404830430">"तपाईँका सम्पर्कहरू परिवर्तन गर्नुहोस्"</string> + <string name="permlab_writeContacts" msgid="8919430536404830430">"तपाईँका कन्ट्याक्टहरू परिवर्तन गर्नुहोस्"</string> <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"एपलाई तपाईंको ट्याब्लेटमा भण्डारण गरिएका सम्पर्क ठेगानासम्बन्धी डेटा परिमार्जन गर्न अनुमति दिन्छ। यो अनुमतिले एपलाई सम्पर्क ठेगानासम्बन्धी डेटा मेटाउन अनुमति दिन्छ।"</string> <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"एपलाई तपाईंको Android टिभी डिभाइसमा भण्डारण गरिएका सम्पर्क ठेगानासम्बन्धी डेटा परिमार्जन गर्न अनुमति दिन्छ। यो अनुमतिले एपलाई सम्पर्क ठेगानासम्बन्धी डेटा मेटाउन अनुमति दिन्छ।"</string> <string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"एपलाई तपाईंको फोनमा भण्डारण गरिएका सम्पर्क ठेगानासम्बन्धी डेटा परिमार्जन गर्न अनुमति दिन्छ। यो अनुमतिले एपलाई सम्पर्क ठेगानासम्बन्धी डेटा मेटाउन अनुमति दिन्छ।"</string> @@ -645,7 +645,7 @@ <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"बायोमेट्रिक हार्डवेयर उपलब्ध छैन"</string> <string name="biometric_error_user_canceled" msgid="6732303949695293730">"प्रमाणीकरण रद्द गरियो"</string> <string name="biometric_not_recognized" msgid="5106687642694635888">"पहिचान भएन"</string> - <string name="biometric_face_not_recognized" msgid="5535599455744525200">"अनुहार पहिचान गर्न सकिएन"</string> + <string name="biometric_face_not_recognized" msgid="5535599455744525200">"अनुहार मिलेन"</string> <string name="biometric_error_canceled" msgid="8266582404844179778">"प्रमाणीकरण रद्द गरियो"</string> <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"कुनै पनि PIN, ढाँचा वा पासवर्ड सेट गरिएको छैन"</string> <string name="biometric_error_generic" msgid="6784371929985434439">"प्रमाणित गर्ने क्रममा त्रुटि भयो"</string> @@ -666,7 +666,7 @@ </string-array> <string name="fingerprint_error_not_match" msgid="4599441812893438961">"फिंगरप्रिन्ट मिलेन"</string> <string name="fingerprint_udfps_error_not_match" msgid="8236930793223158856">"फिंगरप्रिन्ट मिलेन"</string> - <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5590293588784953188">"अनुहार पहिचान गर्न सकिएन। बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्।"</string> + <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5590293588784953188">"अनुहार मिलेन। बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्।"</string> <string name="fingerprint_authenticated" msgid="2024862866860283100">"फिंगरप्रिन्ट प्रमाणीकरण गरियो"</string> <string name="face_authenticated_no_confirmation_required" msgid="8867889115112348167">"अनुहार प्रमाणीकरण गरियो"</string> <string name="face_authenticated_confirmation_required" msgid="6872632732508013755">"अनुहार प्रमाणीकरण गरियो, कृपया पुष्टि गर्नुहोस् थिच्नुहोस्"</string> @@ -716,7 +716,7 @@ <string name="face_acquired_not_detected" msgid="1057966913397548150">"तपाईंको अनुहार देखिएन। तपाईंको फोन आफ्नो आँखाअघि राखी समात्नुहोस्।"</string> <string name="face_acquired_too_much_motion" msgid="8199691445085189528">"अत्यधिक हल्लियो। फोन स्थिर राख्नुहोस्।"</string> <string name="face_acquired_recalibrate" msgid="8724013080976469746">"कृपया आफ्नो अनुहार पुनः दर्ता गर्नुहोस्।"</string> - <string name="face_acquired_too_different" msgid="4505278456634706967">"अनुहार पहिचान गर्न सकिएन। फेरि प्रयास गर्नुहोस्।"</string> + <string name="face_acquired_too_different" msgid="4505278456634706967">"अनुहार मिलेन। फेरि प्रयास गर्नुहोस्।"</string> <string name="face_acquired_too_similar" msgid="8882920552674125694">"आफ्नो टाउको थोरै यताउता सार्नुहोस्"</string> <string name="face_acquired_pan_too_extreme" msgid="5417928604710621088">"आफ्नो फोनमा अझ सीधा हेर्नुहोस्"</string> <string name="face_acquired_tilt_too_extreme" msgid="5715715666540716620">"आफ्नो फोनमा अझ सीधा हेर्नुहोस्"</string> @@ -1507,7 +1507,7 @@ <string name="ime_action_previous" msgid="6548799326860401611">"अघिल्लो"</string> <string name="ime_action_default" msgid="8265027027659800121">"चलाउनुहोस्"</string> <string name="dial_number_using" msgid="6060769078933953531">\n"नम्बर डायल गर्नुहोस् <xliff:g id="NUMBER">%s</xliff:g> प्रयोग गरेर"</string> - <string name="create_contact_using" msgid="6200708808003692594">"सम्पर्क सिर्जना गर्नुहोस्\nयो <xliff:g id="NUMBER">%s</xliff:g> प्रयोग गरेर"</string> + <string name="create_contact_using" msgid="6200708808003692594">"कन्ट्याक्ट हाल्नुहोस्\nयो <xliff:g id="NUMBER">%s</xliff:g> प्रयोग गरेर"</string> <string name="grant_credentials_permission_message_header" msgid="5365733888842570481">"निम्न एउटा वा धेरै एपहरूले तपाईँको खातामा पहुँचको लागि अनुमति अहिले र भविष्यमा अनुरोध गर्छन्।"</string> <string name="grant_credentials_permission_message_footer" msgid="1886710210516246461">"के तपाईं यस अनुरोधलाई अनुमति दिन चाहनुहुन्छ?"</string> <string name="grant_permissions_header_text" msgid="3420736827804657201">"अनुरोध पहुँच गर्नुहोस्"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages खोल्नुहोस्"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"यसले काम गर्ने तरिका"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"विचाराधीन..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 5c393af52d37..7bae96e6ab88 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -159,9 +159,9 @@ <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Om <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> heeft een netwerk in de buurt de unieke ID van je apparaat (IMSI of IMEI) geregistreerd toen je je simkaart van <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> gebruikte"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Om <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> heeft een netwerk in de buurt de unieke ID van je apparaat (IMSI of IMEI) geregistreerd toen je je simkaart van <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> gebruikte.\n\nDit betekent dat je locatie, activiteit of identiteit is geregistreerd. Dit is gebruikelijk, maar kan een probleem zijn voor mensen die zich zorgen maken over privacy."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Verbonden met versleuteld netwerk <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> - <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"De verbinding van de simkaart van <xliff:g id="NETWORK_NAME">%1$s</xliff:g> is nu beter beveiligd"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"De verbinding van de <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-sim is nu beter beveiligd"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Verbonden met een niet-versleuteld netwerk"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Gesprekken, berichten en gegevens zijn op dit moment kwetsbaarder tijdens het gebruik van je simkaart van <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Gesprekken, berichten en gegevens zijn op dit moment kwetsbaarder tijdens het gebruik van je <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-sim"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Gesprekken, berichten en gegevens zijn op dit moment kwetsbaarder tijdens het gebruik van je simkaart van <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nAls je verbinding weer versleuteld is, krijg je opnieuw een melding."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Instellingen voor beveiliging van mobiel netwerk"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Meer informatie"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Berichten openen"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Hoe het werkt"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"In behandeling…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index e6fa56248b0f..c00998acbe16 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -156,7 +156,7 @@ <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"ମୋବାଇଲ ନେଟୱାର୍କ ସୁରକ୍ଷା"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"ଏନକ୍ରିପସନ, ଏନକ୍ରିପ୍ଟ କରାଯାଇନଥିବା ନେଟୱାର୍କ ପାଇଁ ବିଜ୍ଞପ୍ତି"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"ଡିଭାଇସ ID ଆକ୍ସେସ କରାଯାଇଛି"</string> - <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"ଆପଣଙ୍କ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> ରେକର୍ଡ କରିବା ସମୟରେ <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>ରେ ଏକ ଆଖପାଖର ନେଟୱାର୍କ ଆପଣଙ୍କ ଡିଭାଇସର ସ୍ୱତନ୍ତ୍ର ID (IMSI କିମ୍ବା IMEI) ରେକର୍ଡ କରିଛି"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"ଆପଣଙ୍କ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM ବ୍ୟବହାର କରିବା ସମୟରେ <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>ରେ ଏକ ଆଖପାଖର ନେଟୱାର୍କ ଆପଣଙ୍କ ଡିଭାଇସର ସ୍ୱତନ୍ତ୍ର ID (IMSI କିମ୍ବା IMEI) ରେକର୍ଡ କରିଛି"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"ଆପଣଙ୍କ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM ବ୍ୟବହାର କରିବା ସମୟରେ <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>ରେ ଏକ ଆଖପାଖର ନେଟୱାର୍କ ଆପଣଙ୍କ ଡିଭାଇସର ସ୍ୱତନ୍ତ୍ର ID (IMSI କିମ୍ବା IMEI) ରେକର୍ଡ କରିଛି। \n\nଏହାର ଅର୍ଥ ହେଉଛି ଯେ ଆପଣଙ୍କ ଲୋକେସନ, କାର୍ଯ୍ୟକଳାପ କିମ୍ବା ପରିଚୟକୁ ଲଗ କରାଯାଇଛି। ଏହା ସାଧାରଣ ଅଭ୍ୟାସ ଅଟେ କିନ୍ତୁ ଗୋପନୀୟତା ବିଷୟରେ ଚିନ୍ତିତ ଲୋକମାନଙ୍କ ପାଇଁ ଏହା ଏକ ସମସ୍ୟା ହୋଇପାରେ।"</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"ଏନକ୍ରିପ୍ଟ କରାଯାଇଥିବା ନେଟୱାର୍କ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ସହ କନେକ୍ଟ କରାଯାଇଛି"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM କନେକ୍ସନ ବର୍ତ୍ତମାନ ଅଧିକ ସୁରକ୍ଷିତ ଅଟେ"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ଖୋଲନ୍ତୁ"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ଏହା କିପରି କାମ କରେ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"ବାକି ଅଛି…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index e2586e6a9b78..77020acff650 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -161,8 +161,8 @@ <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"ਇਨਕ੍ਰਿਪਟਡ ਨੈੱਟਵਰਕ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> ਸਿਮ ਕਨੈਕਸ਼ਨ ਹੁਣ ਜ਼ਿਆਦਾ ਸੁਰੱਖਿਅਤ ਹੈ"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"ਇਨਕ੍ਰਿਪਟਡ ਨੈੱਟਵਰਕ ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"ਫ਼ਿਲਹਾਲ ਤੁਹਾਡੇ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ਸਿਮ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਸਮੇਂ ਕਾਲਾਂ, ਸੁਨੇਹਿਆਂ ਅਤੇ ਡਾਟਾ ਜ਼ਿਆਦਾ ਖਤਰੇ ਵਿੱਚ ਹਨ"</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"ਫ਼ਿਲਹਾਲ ਤੁਹਾਡੇ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ਸਿਮ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਸਮੇਂ ਕਾਲਾਂ, ਸੁਨੇਹਿਆਂ ਅਤੇ ਡਾਟਾ ਜ਼ਿਆਦਾ ਖਤਰੇ ਵਿੱਚ ਹਨ।\n\nਤੁਹਾਡਾ ਕਨੈਕਸ਼ਨ ਦੁਬਾਰਾ ਇਨਕ੍ਰਿਪਟਡ ਹੋਣ \'ਤੇ, ਤੁਹਾਨੂੰ ਹੋਰ ਸੂਚਨਾ ਪ੍ਰਾਪਤ ਹੋਵੇਗੀ।"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"ਫ਼ਿਲਹਾਲ ਤੁਹਾਡੇ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ਸਿਮ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਸਮੇਂ ਕਾਲਾਂ, ਸੁਨੇਹਿਆਂ ਅਤੇ ਡਾਟੇ ਤੱਕ ਪਹੁੰਚ ਕੀਤੇ ਜਾਣ ਦਾ ਖਤਰਾ ਰਹਿੰਦਾ ਹੈ"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"ਫ਼ਿਲਹਾਲ ਤੁਹਾਡੇ <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ਸਿਮ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਸਮੇਂ ਕਾਲਾਂ, ਸੁਨੇਹਿਆਂ ਅਤੇ ਡਾਟੇ ਤੱਕ ਪਹੁੰਚ ਕੀਤੇ ਜਾਣ ਦਾ ਖਤਰਾ ਰਹਿੰਦਾ ਹੈ।\n\nਤੁਹਾਡਾ ਕਨੈਕਸ਼ਨ ਦੁਬਾਰਾ ਇਨਕ੍ਰਿਪਟਡ ਹੋਣ \'ਤੇ, ਤੁਹਾਨੂੰ ਹੋਰ ਸੂਚਨਾ ਪ੍ਰਾਪਤ ਹੋਵੇਗੀ।"</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"ਮੋਬਾਈਲ ਨੈੱਟਵਰਕ ਸੁਰੱਖਿਆ ਸੰਬੰਧੀ ਸੈਟਿੰਗਾਂ"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"ਹੋਰ ਜਾਣੋ"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"ਸਮਝ ਲਿਆ"</string> @@ -316,7 +316,7 @@ <string name="managed_profile_app_label" msgid="367401088383965725">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਦੇ ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ \'ਤੇ ਜਾਓ"</string> <string name="permgrouplab_contacts" msgid="4254143639307316920">"ਸੰਪਰਕ"</string> <string name="permgroupdesc_contacts" msgid="9163927941244182567">"ਆਪਣੇ ਸੰਪਰਕਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ"</string> - <string name="permgrouplab_location" msgid="1858277002233964394">"ਟਿਕਾਣਾ"</string> + <string name="permgrouplab_location" msgid="1858277002233964394">"ਟਿਕਾਣੇ ਦੀ ਜਾਣਕਾਰੀ"</string> <string name="permgroupdesc_location" msgid="1995955142118450685">"ਇਸ ਡੀਵਾਈਸ ਦੇ ਨਿਰਧਾਰਤ ਟਿਕਾਣੇ ਤੱਕ ਪਹੁੰਚੋ"</string> <string name="permgrouplab_calendar" msgid="6426860926123033230">"ਕੈਲੰਡਰ"</string> <string name="permgroupdesc_calendar" msgid="6762751063361489379">"ਤੁਹਾਡੇ ਕੈਲੰਡਰ ਤੱਕ ਪਹੁੰਚ ਕਰਨ"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ਐਪ ਖੋਲ੍ਹੋ"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ਇਹ ਕਿਵੇਂ ਕੰਮ ਕਰਦਾ ਹੈ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"ਵਿਚਾਰ-ਅਧੀਨ..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index d5c8d763bf0e..0971ae40d500 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -161,9 +161,9 @@ <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"O godzinie <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> pobliska sieć zarejestrowała unikalny identyfikator Twojego urządzenia (IMSI lub IMEI) podczas korzystania z karty SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"O godzinie <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> pobliska sieć zarejestrowała unikalny identyfikator Twojego urządzenia (IMSI lub IMEI) podczas korzystania z karty SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nOznacza to, że zarejestrowano Twoją lokalizację, aktywność lub tożsamość. Jest to powszechna praktyka, ale może stanowić problem dla osób dbających o prywatność."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Połączono z szyfrowaną siecią <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> - <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Połączenie SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> jest teraz bezpieczniejsze"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Połączenie przy użyciu karty SIM w sieci <xliff:g id="NETWORK_NAME">%1$s</xliff:g> jest teraz bezpieczniejsze"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Połączono z niezaszyfrowaną siecią"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Połączenia, wiadomości i dane są obecnie bardziej podatne na ataki podczas korzystania z karty SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Gdy korzystasz z karty SIM w sieci <xliff:g id="NETWORK_NAME">%1$s</xliff:g>, połączenia, wiadomości i dane są bardziej narażone na ataki"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Połączenia, wiadomości i dane są obecnie bardziej podatne na ataki podczas korzystania z karty SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nGdy połączenie zostanie ponownie zaszyfrowane, otrzymasz kolejne powiadomienie."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Ustawienia bezpieczeństwa sieci komórkowej"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Więcej informacji"</string> @@ -2415,4 +2415,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otwórz Wiadomości"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Jak to działa"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Oczekiwanie…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index 4d9e208233a8..b04cd4c6ed98 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -164,7 +164,7 @@ <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Conectado à rede não criptografada"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Suas ligações, mensagens e dados estão mais vulneráveis no momento ao usar seu chip da <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Ligações, mensagens e dados estão mais vulneráveis no momento ao usar seu chip da <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nQuando a conexão for criptografada novamente, você vai receber outra notificação."</string> - <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Configurações de segurança de rede móvel"</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Configurações de segurança da rede móvel"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Saiba mais"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Entendi"</string> <string name="fcComplete" msgid="1080909484660507044">"Código de recurso concluído."</string> @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir o app Mensagens"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendente…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 2074d1e85868..90f9eda9c32e 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -1389,7 +1389,7 @@ <string name="no_permissions" msgid="5729199278862516390">"Não são necessárias permissões"</string> <string name="perm_costs_money" msgid="749054595022779685">"isto poderá estar sujeito a custos"</string> <string name="dlg_ok" msgid="5103447663504839312">"OK"</string> - <string name="usb_charging_notification_title" msgid="1674124518282666955">"O dispositivo está a carregar por USB"</string> + <string name="usb_charging_notification_title" msgid="1674124518282666955">"O dispositivo está a ser carregado por USB"</string> <string name="usb_supplying_notification_title" msgid="5378546632408101811">"A carregar o dispositivo ligado por USB"</string> <string name="usb_mtp_notification_title" msgid="1065989144124499810">"A transferência de ficheiros por USB está ativada"</string> <string name="usb_ptp_notification_title" msgid="5043437571863443281">"O PTP por USB está ativado"</string> @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abre a app Mensagens"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendente…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index 4d9e208233a8..b04cd4c6ed98 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -164,7 +164,7 @@ <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Conectado à rede não criptografada"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Suas ligações, mensagens e dados estão mais vulneráveis no momento ao usar seu chip da <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Ligações, mensagens e dados estão mais vulneráveis no momento ao usar seu chip da <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nQuando a conexão for criptografada novamente, você vai receber outra notificação."</string> - <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Configurações de segurança de rede móvel"</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Configurações de segurança da rede móvel"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Saiba mais"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Entendi"</string> <string name="fcComplete" msgid="1080909484660507044">"Código de recurso concluído."</string> @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abrir o app Mensagens"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendente…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index 10aa0207bf85..c43df4fa6e41 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Deschide Mesaje"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cum funcționează"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"În așteptare..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index ff0d1b8818ca..32a2338b284d 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -158,11 +158,11 @@ <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Безопасность мобильной сети"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Шифрование, уведомления о незашифрованных сетях"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Получен доступ к идентификатору устройства"</string> - <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"В <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> при использовании SIM-карты (<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>) в сети поблизости был записан уникальный идентификатор вашего устройства (IMSI или IMEI)."</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"В <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> при использовании SIM-карты (<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>) в сети поблизости был записан уникальный идентификатор вашего устройства (IMSI или IMEI).\n\nВаше местоположение, действия или личность были зарегистрированы. Хотя в этом нет ничего необычного, раскрытие данных может доставлять проблемы людям, которые беспокоятся о своей конфиденциальности."</string> - <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Подключение к зашифрованной сети \"<xliff:g id="NETWORK_NAME">%1$s</xliff:g>\""</string> - <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Теперь подключение SIM-карты (<xliff:g id="NETWORK_NAME">%1$s</xliff:g>) более безопасное."</string> - <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Подключение к незашифрованной сети"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"В <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, когда вы использовали SIM-карту \"<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>\", сетью поблизости от вас был записан уникальный идентификатор вашего устройства (IMSI или IMEI)."</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"В <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, когда вы использовали SIM-карту \"<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>\", сетью поблизости от вас был записан уникальный идентификатор вашего устройства (IMSI или IMEI).\n\nЭто значит, что было зарегистрировано ваше местоположение, действия или личность. Это распространенная практика, однако она может вызывать беспокойство у тех, кто заботится о своей конфиденциальности."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Подключено к зашифрованной сети <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Теперь подключение SIM-карты (<xliff:g id="NETWORK_NAME">%1$s</xliff:g>) лучше защищено."</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Подключено к незашифрованной сети"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Сейчас звонки, сообщения и данные более уязвимы при использовании SIM-карты (<xliff:g id="NETWORK_NAME">%1$s</xliff:g>)."</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Сейчас звонки, сообщения и данные более уязвимы при использовании SIM-карты (<xliff:g id="NETWORK_NAME">%1$s</xliff:g>).\n\nКогда соединение будет снова зашифровано, вы получите ещё одно уведомление."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Настройки безопасности мобильной сети"</string> @@ -2415,4 +2415,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Открыть Сообщения"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Узнать принцип работы"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Обработка…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml index 18b26f27816b..9539cef0f26c 100644 --- a/core/res/res/values-si/strings.xml +++ b/core/res/res/values-si/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages විවෘත කරන්න"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"එය ක්රියා කරන ආකාරය"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"පොරොත්තුයි..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index 3b144d27905f..c72a426d0436 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -163,8 +163,8 @@ <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Pripojené k šifrovanej sieti <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Pripojenie SIM siete <xliff:g id="NETWORK_NAME">%1$s</xliff:g> je teraz zabezpečenejšie"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Pripojené k nešifrovanej sieti"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Hovory, správy a údaje sú momentálne zraniteľnejšie vzhľadom na nedostatok zabezpečenia pri používaní SIM siete <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Hovory, správy a údaje sú momentálne zraniteľnejšie vzhľadom na nedostatok zabezpečenia pri používaní SIM siete <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nKeď bude vaše pripojenie znova šifrované, dostanete ďalšie upozornenie."</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Pri používaní SIM siete <xliff:g id="NETWORK_NAME">%1$s</xliff:g> sú vaše hovory, správy a údaje zraniteľnejšie"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Pri používaní SIM siete <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nsú vaše hovory, správy a údaje zraniteľnejšie. Keď bude vaše pripojenie znova šifrované, dostanete ďalšie upozornenie."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Nastavenia zabezpečenia mobilnej siete"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Ďalšie informácie"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Dobre"</string> @@ -2415,4 +2415,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Otvoriť Správy"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ako to funguje"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Nespracovaná…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index 1b7a2a145f80..e1be803cce1b 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -2415,4 +2415,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Odpri Sporočila"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Kako deluje"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"V teku …"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml index 142ab0d663d6..4d79ce707787 100644 --- a/core/res/res/values-sq/strings.xml +++ b/core/res/res/values-sq/strings.xml @@ -157,12 +157,12 @@ <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Enkriptimi, njoftimet për rrjetet e paenkriptuara"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Pati qasje tek ID-ja e pajisjes"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Në <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, një rrjet në afërsi ka regjistruar ID-në unike (IMSI ose IMEI) të pajisjes sate gjatë përdorimit të kartës sate SIM të <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Në <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, një rrjet në afërsi ka regjistruar ID-në unike (IMSI ose IMEI) të pajisjes sate gjatë përdorimit të kartës sate SIM të <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nKjo do të thotë që vendndodhja, aktiviteti ose identiteti yt janë regjistruar. Kjo është një praktikë të zakonshme, por mund të jetë problem për personat që janë të shqetësuar në lidhje me privatësinë."</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Në <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, një rrjet në afërsi ka regjistruar ID-në unike (IMSI ose IMEI) të pajisjes sate gjatë përdorimit të kartës sate SIM të <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nKjo do të thotë që vendndodhja, aktiviteti ose identiteti yt janë regjistruar. Kjo është një praktikë e zakonshme, por mund të jetë problem për personat që janë të shqetësuar në lidhje me privatësinë."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Lidhur me rrjetin e enkriptuar <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Lidhja e kartës SIM të <xliff:g id="NETWORK_NAME">%1$s</xliff:g> është më e sigurt tani"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Lidhur me një rrjet të paenkriptuar"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Telefonatat, mesazhet dhe të dhënat janë aktualisht më të cenueshme ndërkohë që përdoret karta jote SIM të <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Telefonatat, mesazhet dhe të dhënat janë aktualisht më të cenueshme ndërkohë që përdoret karta jote SIM të <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nKur lidhja jote të jetë përsëri e enkriptuar, do të marrësh një njoftim tjetër."</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Telefonatat, mesazhet dhe të dhënat janë aktualisht më të cenueshme teksa përdoret karta SIM e <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Telefonatat, mesazhet dhe të dhënat janë aktualisht më të cenueshme teksa përdoret karta SIM e <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nKur lidhja jote të jetë përsëri e enkriptuar, do të marrësh një njoftim tjetër."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Cilësimet e sigurisë së rrjetit celular"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Mëso më shumë"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"E kuptova"</string> @@ -2144,10 +2144,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Njoftimi i informacionit të \"Modalitetit rutinë\""</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"\"Kursyesi i baterisë\" u aktivizua"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Po reduktohet përdorimi i baterisë për të rritur kohëzgjatjen e baterisë"</string> - <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) --> - <skip /> - <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) --> - <skip /> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"\"Kursyesi i baterisë\" është aktiv"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"\"Kursyesi i baterisë\" është aktivizuar për të zgjatur kohëzgjatjen e baterisë"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Kursyesi i baterisë"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"\"Kursyesi i baterisë\" është çaktivizuar"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefoni ka nivel të mjaftueshëm baterie. Funksionet nuk janë më të kufizuara."</string> @@ -2415,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Hap \"Mesazhet\""</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Si funksionon"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Në pritje..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index 87d10860a0b0..95ccead79ce2 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -161,7 +161,7 @@ <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Мрежа у близини је у <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> евидентирала јединствени ИД вашег уређаја (IMSI или IMEI) док сте користили <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM.\n\nТо значи да је евидентирала вашу локацију, активност и идентитет. То је уобичајена пракса, али може да буде проблем људима који су забринути за приватност."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Повезани сте на шифровану мрежу <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Веза <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM-а је сада безбеднија"</string> - <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Повезани сте на шифровану мрежу"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Повезани сте на нешифровану мрежу"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Позиви, поруке и подаци су тренутно рањивији док користите <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Позиви, поруке и подаци су тренутно рањивији док користите <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM.\n\nКада веза поново буде шифрована, послаћемо вам друго обавештење."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Подешавања безбедности на мобилној мрежи"</string> @@ -2414,4 +2414,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Отвори Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Принцип рада"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"На чекању..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 3ca5c9fcb4ab..0cbdda1a4c27 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Öppna Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Så fungerar det"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Väntar …"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index 2efca2b4a891..10f55af3dc47 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -161,8 +161,8 @@ <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Imeunganishwa kwenye mtandao uliosimbwa kwa njia fiche wa <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Sasa muunganisho wa SIM wa <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ni salama zaidi"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Imeunganishwa kwenye mtandao ambao haujasimbwa kwa njia fiche"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Kwa sasa maudhui ya simu, ujumbe na data yako katika hatari ya kuvamiwa unapotumia SIM yako ya <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> - <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Kwa sasa maudhui ya simu, ujumbe na data yako katika hatari ya kuvamiwa unapotumia SIM yako ya <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nMuunganisho wako ukisimbwa kwa njia fiche tena, utapata arifa nyingine."</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Kwa sasa maudhui ya simu, ujumbe na data ipo katika hatari ya kuvamiwa unapotumia SIM yako ya <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Kwa sasa maudhui ya simu, ujumbe na data ipo katika hatari ya kuvamiwa unapotumia SIM yako ya <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nMuunganisho wako ukisimbwa kwa njia fiche tena, utapata arifa nyingine."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Mipangilio ya usalama wa mtandao wa simu"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Pata maelezo zaidi"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Nimeelewa"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Fungua Programu ya Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Utaratibu wake"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Inashughulikiwa..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml index 0c704e353836..466e29a7e9b1 100644 --- a/core/res/res/values-ta/strings.xml +++ b/core/res/res/values-ta/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messages ஆப்ஸைத் திறக்கவும்"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"இது செயல்படும் விதம்"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"நிலுவையிலுள்ளது..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index b465000e9690..db75aac8b027 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -156,7 +156,7 @@ <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"మొబైల్ నెట్వర్క్ సెక్యూరిటీ"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"ఎన్క్రిప్షన్, ఎన్క్రిప్ట్ చేయని నెట్వర్క్లకు సంబంధించిన నోటిఫికేషన్లు"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"పరికర IDని యాక్సెస్ చేశారు"</string> - <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"మీ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIMను ఉపయోగిస్తున్నప్పుడు, సమీపంలోని నెట్వర్క్ <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> సమయానికి మీ పరికర యూనిక్ ID (IMSI లేదా IMEI)ని రికార్డ్ చేసింది"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"మీ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIMను వాడేటప్పుడు, <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>కి దగ్గర్లోని ఒక నెట్వర్క్, మీ పరికర యూనిక్ IDని (IMSI లేదా IMEI) రికార్డ్ చేసింది"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"మీ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIMను ఉపయోగిస్తున్నప్పుడు, సమీపంలోని నెట్వర్క్ <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> సమయానికి మీ పరికర యూనిక్ ID (IMSI లేదా IMEI)ని రికార్డ్ చేసింది.\n\nమీ లొకేషన్, యాక్టివిటీ, లేదా గుర్తింపు లాగ్ అయ్యాయని దీని అర్థం. దీనిని తరచుగా అమలు చేస్తుంటారు, కానీ గోప్యత గురించి ఆందోళనపడే వ్యక్తులకు ఇది సమస్య కావచ్చు,"</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"ఎన్క్రిప్ట్ చేసిన నెట్వర్క్ <xliff:g id="NETWORK_NAME">%1$s</xliff:g>కు కనెక్ట్ అయింది"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM కనెక్షన్ ఇప్పుడు మరింత సురక్షితంగా ఉంది"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Messagesను తెరవండి"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"ఇది ఎలా పని చేస్తుంది"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"పెండింగ్లో ఉంది..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index 535396de7936..284a5f273f22 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -1619,7 +1619,7 @@ <string name="data_usage_mobile_limit_snoozed_title" msgid="101888478915677895">"เกินปริมาณเน็ตมือถือที่กำหนดไว้"</string> <string name="data_usage_wifi_limit_snoozed_title" msgid="1622359254521960508">"เกินขีดจำกัดของข้อมูล Wi-Fi"</string> <string name="data_usage_limit_snoozed_body" msgid="545146591766765678">"คุณใช้อินเทอร์เน็ตเกินไป <xliff:g id="SIZE">%s</xliff:g> จากปริมาณที่กำหนดไว้"</string> - <string name="data_usage_restricted_title" msgid="126711424380051268">"จำกัดอินเทอร์เน็ตที่ใช้งานอยู่เบื้องหลัง"</string> + <string name="data_usage_restricted_title" msgid="126711424380051268">"จำกัดข้อมูลในเบื้องหลัง"</string> <string name="data_usage_restricted_body" msgid="5338694433686077733">"แตะเพื่อนำข้อจำกัดออก"</string> <string name="data_usage_rapid_title" msgid="2950192123248740375">"ปริมาณการใช้เน็ตมือถือสูง"</string> <string name="data_usage_rapid_body" msgid="3886676853263693432">"แอปของคุณใช้อินเทอร์เน็ตมากกว่าปกติ"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"เปิด Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"วิธีการทำงาน"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"รอดำเนินการ..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index 6eab396ca97c..4dd488681f5b 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -161,7 +161,7 @@ <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Nakakonekta sa naka-encrypt na network na <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Mas secure na ang koneksyon ng <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Nakakonekta sa hindi naka-encrypt na network"</string> - <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Kasalukuyang mas nanganganib ang mga tawag, mensahe, at data habang ginagamit ang iyong <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Kasalukuyang walang proteksyon ang tawag, mensahe, at data habang gamit ang <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM mo"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Kasalukuyang mas nanganganib ang mga tawag, mensahe, at data habang ginagamit ang iyong <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM.\n\nKapag na-encrypt ulit ang iyong koneksyon, makakatanggap ka ng isa pang notification."</string> <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Mga setting ng seguridad ng mobile network"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Matuto pa"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Buksan ang Messages"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Paano ito gumagana"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Nakabinbin..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index 4c8083a754a5..2b616defdaa6 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Mesajlar\'ı aç"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"İşleyiş şekli"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Bekliyor..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index 6f94a2cf4e51..850e21b15ef9 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -2146,10 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Сповіщення про програму"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Режим енергозбереження ввімкнено"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Заряд використовується економно, щоб подовжити час роботи акумулятора"</string> - <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) --> - <skip /> - <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) --> - <skip /> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Режим енергозбереження ввімкнено"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Увімкнено режим енергозбереження, щоб збільшити час роботи акумулятора"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Режим енергозбереження"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Режим енергозбереження вимкнено."</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Телефон має достатньо заряду акумулятора. Функції вже не обмежено."</string> @@ -2417,4 +2415,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Відкрийте Повідомлення"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Як це працює"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Обробка…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index ed1a358d19a8..d3fde3c4f455 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -157,7 +157,7 @@ <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"غیر مرموز کردہ نیٹ ورکس کے لیے مرموز کاری، اطلاعات"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"آلہ ID تک رسائی کی گئی"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> میں، قریبی نیٹ ورک نے آپ کے <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM کا استعمال کرتے ہوئے آپ کے آلے کی منفرد ID (IMSI یا IMEI) ریکارڈ کی"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> میں، آپ کے <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM استعمال کرتے ہوئے قریبی نیٹ ورک نے آپ کے آلے کی منفرد ID (IMSI یا IMEI) کو ریکارڈ کیا۔\n\nاس کا مطلب ہے کہ آپ کا مقام، سرگرمی یا شناخت لاگ ان ہو چکی ہیں۔ یہ عام بات ہے لیکن رازداری کے بارے میں فکر مند لوگوں کے لیے ایک مسئلہ ہو سکتا ہے۔"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> میں، آپ کے <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM استعمال کرتے ہوئے قریبی نیٹ ورک نے آپ کے آلے کی منفرد ID (IMSI یا IMEI) کو ریکارڈ کیا۔\n\nاس کا مطلب ہے کہ آپ کا مقام، سرگرمی یا شناخت کو ریکارڈ کیا جا چکا ہے۔ یہ عام بات ہے لیکن رازداری کے بارے میں فکر مند لوگوں کے لیے ایک مسئلہ ہو سکتا ہے۔"</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"مرموز کردہ نیٹ ورک <xliff:g id="NETWORK_NAME">%1$s</xliff:g> سے منسلک ہے"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM کا کنکشن اب بہت محفوظ ہے"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"غیر مرموز کردہ نیٹ ورک سے منسلک ہے"</string> @@ -644,7 +644,7 @@ <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"جاری رکھنے کے لیے اپنے بایو میٹرک اور اسکرین لاک کا استعمال کریں"</string> <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"بایومیٹرک ہارڈ ویئر دستیاب نہیں ہے"</string> <string name="biometric_error_user_canceled" msgid="6732303949695293730">"تصدیق کا عمل منسوخ ہو گیا"</string> - <string name="biometric_not_recognized" msgid="5106687642694635888">"تسلیم شدہ نہیں ہے"</string> + <string name="biometric_not_recognized" msgid="5106687642694635888">"شناخت نہیں ہو سکی"</string> <string name="biometric_face_not_recognized" msgid="5535599455744525200">"چہرے کی شناخت نہیں ہو سکی"</string> <string name="biometric_error_canceled" msgid="8266582404844179778">"تصدیق کا عمل منسوخ ہو گیا"</string> <string name="biometric_error_device_not_secured" msgid="3129845065043995924">"کوئی پن، پیٹرن، یا پاس ورڈ سیٹ نہیں ہے"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"پیغامات ایپ کو کھولیں"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"اس کے کام کرنے کا طریقہ"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"زیر التواء..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml index 4ddd368766c8..c143244db230 100644 --- a/core/res/res/values-uz/strings.xml +++ b/core/res/res/values-uz/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Xabarlar ilovasini ochish"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Ishlash tartibi"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Kutilmoqda..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index c633c2af0a15..a0775d400d5d 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -155,15 +155,15 @@ <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Không được chuyển tiếp"</string> <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Bảo mật mạng di động"</string> <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Mã hoá, thông báo cho mạng chưa được mã hoá"</string> - <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Mã thiết bị được truy cập"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Mã thiết bị đã bị truy cập"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Vào <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, một mạng lân cận đã ghi nhận mã nhận dạng duy nhất của thiết bị của bạn (IMSI hoặc IMEI) trong lúc sử dụng SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> của bạn"</string> - <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Vào <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, một mạng lân cận đã ghi nhận mã nhận dạng duy nhất của thiết bị của bạn (IMSI hoặc IMEI) trong lúc sử dụng SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> của bạn.\n\nTức là vị trí, hoạt động hoặc danh tính của bạn đã được ghi lại. Đây là một phương thức thông dụng nhưng có thể trở thành vấn đề đối với những ai lo ngại về quyền riêng tư."</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Vào <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, một mạng lân cận đã ghi lại mã nhận dạng duy nhất của thiết bị của bạn (IMSI hoặc IMEI) trong lúc sử dụng SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> của bạn.\n\nTức là vị trí, hoạt động hoặc danh tính của bạn đã được ghi lại. Đây là một phương thức thông dụng nhưng có thể trở thành vấn đề đối với những ai lo ngại về quyền riêng tư."</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Đã kết nối với mạng <xliff:g id="NETWORK_NAME">%1$s</xliff:g> đã mã hoá"</string> <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Kết nối SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> nay an toàn hơn"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Đã kết nối với mạng chưa được mã hoá"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Cuộc gọi, tin nhắn và dữ liệu hiện dễ bị tấn công trong lúc sử dụng SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> của bạn."</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Cuộc gọi, tin nhắn và dữ liệu hiện dễ bị tấn công trong lúc sử dụng SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> của bạn.\n\nKhi kết nối của bạn được mã hoá lại, bạn sẽ nhận được một thông báo khác."</string> - <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Chế độ bảo mật mạng di động"</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Cài đặt an ninh mạng di động"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Tìm hiểu thêm"</string> <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Tôi hiểu"</string> <string name="fcComplete" msgid="1080909484660507044">"Mã tính năng đã hoàn tất."</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Mở ứng dụng Tin nhắn"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Cách hoạt động"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Đang chờ xử lý..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index f16881a847c3..00dd3db2b35a 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -154,12 +154,12 @@ <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:无法转接"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:无法转接"</string> <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"移动网络安全"</string> - <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"加密,连接到未加密网络时通知"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"加密、通知(连接到未加密网络时)"</string> <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"有网络获取了设备 ID"</string> <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>,当您使用<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM 卡时,一个附近的网络记录了您设备的唯一 ID(IMSI 或 IMEI)"</string> <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>,当您使用<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM 卡时,一个附近的网络记录了您设备的唯一 ID(IMSI 或 IMEI)。\n\n这意味着您的位置信息、活动或身份信息都被记录了。这是常见做法,但对注重隐私的人来说可能是一个问题。"</string> <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"连接到了名为“<xliff:g id="NETWORK_NAME">%1$s</xliff:g>”的加密网络"</string> - <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM 卡连接现在更安全了"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM 卡的连接现在更安全了"</string> <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"连接到了一个未加密的网络"</string> <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"目前,当您使用<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM 卡时,通话、消息和数据更易受到攻击"</string> <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"目前,当您使用<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM 卡时,通话、消息和数据更易受到攻击。\n\n当您的连接再次加密时,您会另收到一条通知。"</string> @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"打开“信息”应用"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"运作方式"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"待归档…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index efa260f3f4e6..142940a86ebb 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"開啟「訊息」"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"運作方式"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"待處理…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index 0c18193fc542..b7ee23c23f21 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -2144,10 +2144,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"日常安排模式資訊通知"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"已開啟省電模式"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"降低電池用量,以便延長電池續航力"</string> - <!-- no translation found for dynamic_mode_notification_title_v2 (5072385242078021152) --> - <skip /> - <!-- no translation found for dynamic_mode_notification_summary_v2 (2142444344663147938) --> - <skip /> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"省電模式已開啟"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"已開啟省電模式,延長電池續航力"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"省電模式"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"省電模式已關閉"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"手機電力充足,各項功能不再受到限制。"</string> @@ -2415,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"開啟「訊息」應用程式"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"運作方式"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"待處理…"</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index 7dc3fd63719e..a967fb6f1ad1 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -2413,4 +2413,22 @@ <string name="satellite_notification_open_message" msgid="4149234979688273729">"Vula Imilayezo"</string> <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Indlela esebenza ngayo"</string> <string name="unarchival_session_app_label" msgid="6811856981546348205">"Ilindile..."</string> + <!-- no translation found for fingerprint_dangling_notification_title (7362075195588639989) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (6261149111900787302) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7688302770424064884) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_1 (2927018569542316055) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_all_deleted_2 (6897989352716156176) --> + <skip /> + <!-- no translation found for face_dangling_notification_title (947852541060975473) --> + <skip /> + <!-- no translation found for face_dangling_notification_msg (8806849376915541655) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_set_up (8246885009807817961) --> + <skip /> + <!-- no translation found for biometric_dangling_notification_action_not_now (8095249216864443491) --> + <skip /> </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index cefc648e45a5..5bd20332f381 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4444,8 +4444,8 @@ <!-- True if camera app should be pinned via Pinner Service --> <bool name="config_pinnerCameraApp">false</bool> - <!-- True if home app should be pinned via Pinner Service --> - <bool name="config_pinnerHomeApp">false</bool> + <!-- Bytes that the PinnerService will pin for Home app --> + <integer name="config_pinnerHomePinBytes">0</integer> <!-- True if assistant app should be pinned via Pinner Service --> <bool name="config_pinnerAssistantApp">false</bool> @@ -4652,6 +4652,19 @@ --> <string-array name="config_companionDeviceCerts" translatable="false"></string-array> + <!-- A list of packages that auto-enable permissions sync feature. + Note that config_companionPermSyncEnabledPackages and config_companionPermSyncEnabledCerts + are parallel arrays. + --> + <string-array name="config_companionPermSyncEnabledPackages" translatable="false"></string-array> + + <!-- A list of SHA256 Certificates corresponding to config_companionPermSyncEnabledPackages. + Note that config_companionPermSyncEnabledPackages and config_companionPermSyncEnabledCerts + are parallel arrays. + Example: "1A:2B:3C:4D" + --> + <string-array name="config_companionPermSyncEnabledCerts" translatable="false"></string-array> + <!-- The package name for the default wellbeing app. This package must be trusted, as it has the permissions to control other applications on the device. @@ -4704,6 +4717,13 @@ <!-- The component name for the default system on-device sandboxed inference service. --> <string name="config_defaultOnDeviceSandboxedInferenceService" translatable="false"></string> + <!-- The broadcast intent name for notifying when the on-device model is loading --> + <string name="config_onDeviceIntelligenceModelLoadedBroadcastKey" translatable="false"></string> + + <!-- The broadcast intent name for notifying when the on-device model has been unloaded --> + <string name="config_onDeviceIntelligenceModelUnloadedBroadcastKey" translatable="false"></string> + + <!-- Component name that accepts ACTION_SEND intents for requesting ambient context consent for wearable sensing. --> <string translatable="false" name="config_defaultWearableSensingConsentComponent"></string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d058fb188d16..ae79a4c68f9e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -675,6 +675,8 @@ <java-symbol type="string" name="config_companionDeviceManagerPackage" /> <java-symbol type="array" name="config_companionDevicePackages" /> <java-symbol type="array" name="config_companionDeviceCerts" /> + <java-symbol type="array" name="config_companionPermSyncEnabledPackages" /> + <java-symbol type="array" name="config_companionPermSyncEnabledCerts" /> <java-symbol type="string" name="config_default_dns_server" /> <java-symbol type="string" name="config_ethernet_iface_regex" /> <java-symbol type="string" name="not_checked" /> @@ -3437,7 +3439,7 @@ <!-- Pinner Service --> <java-symbol type="array" name="config_defaultPinnerServiceFiles" /> <java-symbol type="bool" name="config_pinnerCameraApp" /> - <java-symbol type="bool" name="config_pinnerHomeApp" /> + <java-symbol type="integer" name="config_pinnerHomePinBytes" /> <java-symbol type="bool" name="config_pinnerAssistantApp" /> <java-symbol type="integer" name="config_pinnerWebviewPinBytes" /> @@ -3941,6 +3943,8 @@ <java-symbol type="string" name="config_defaultWearableSensingService" /> <java-symbol type="string" name="config_defaultOnDeviceIntelligenceService" /> <java-symbol type="string" name="config_defaultOnDeviceSandboxedInferenceService" /> + <java-symbol type="string" name="config_onDeviceIntelligenceModelLoadedBroadcastKey" /> + <java-symbol type="string" name="config_onDeviceIntelligenceModelUnloadedBroadcastKey" /> <java-symbol type="string" name="config_retailDemoPackage" /> <java-symbol type="string" name="config_retailDemoPackageSignature" /> diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index 7fb894a9dbe9..30ec940bda8e 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -84,7 +84,6 @@ import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import android.os.SystemProperties; import android.platform.test.annotations.Presubmit; import android.platform.test.flag.junit.SetFlagsRule; import android.text.Spannable; @@ -132,9 +131,6 @@ public class NotificationTest { @Before public void setUp() { mContext = InstrumentationRegistry.getContext(); - // TODO(b/169435530): remove this flag set once resolved. - SystemProperties.set("persist.sysui.notification.builder_extras_override", - Boolean.toString(false)); } @Test @@ -1703,10 +1699,6 @@ public class NotificationTest { // Ensures that extras in a Notification Builder can be updated. @Test public void testExtras_cachedExtrasOverwrittenByUserProvided() { - // Sets the flag to new state. - // TODO(b/169435530): remove this set value once resolved. - SystemProperties.set("persist.sysui.notification.builder_extras_override", - Boolean.toString(true)); Bundle extras = new Bundle(); extras.putCharSequence(EXTRA_TITLE, "test title"); extras.putCharSequence(EXTRA_SUMMARY_TEXT, "summary text"); @@ -1732,10 +1724,6 @@ public class NotificationTest { // Ensures that extras in a Notification Builder can be updated by an extender. @Test public void testExtras_cachedExtrasOverwrittenByExtender() { - // Sets the flag to new state. - // TODO(b/169435530): remove this set value once resolved. - SystemProperties.set("persist.sysui.notification.builder_extras_override", - Boolean.toString(true)); Notification.CarExtender extender = new Notification.CarExtender().setColor(1234); Notification notification = new Notification.Builder(mContext, "test id") @@ -1749,58 +1737,6 @@ public class NotificationTest { assertThat(recoveredExtender.getColor()).isEqualTo(5678); } - // Validates pre-flag flip behavior, that extras in a Notification Builder cannot be updated. - // TODO(b/169435530): remove this test once resolved. - @Test - public void testExtras_cachedExtrasOverwrittenByUserProvidedOld() { - // Sets the flag to old state. - SystemProperties.set("persist.sysui.notification.builder_extras_override", - Boolean.toString(false)); - - Bundle extras = new Bundle(); - extras.putCharSequence(EXTRA_TITLE, "test title"); - extras.putCharSequence(EXTRA_SUMMARY_TEXT, "summary text"); - - Notification.Builder builder = new Notification.Builder(mContext, "test id") - .addExtras(extras); - - Notification notification = builder.build(); - assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo( - "test title"); - assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo( - "summary text"); - - extras.putCharSequence(EXTRA_TITLE, "new title"); - builder.addExtras(extras); - notification = builder.build(); - assertThat(notification.extras.getCharSequence(EXTRA_TITLE).toString()).isEqualTo( - "test title"); - assertThat(notification.extras.getCharSequence(EXTRA_SUMMARY_TEXT).toString()).isEqualTo( - "summary text"); - } - - // Validates pre-flag flip behavior, that extras in a Notification Builder cannot be updated - // by an extender. - // TODO(b/169435530): remove this test once resolved. - @Test - public void testExtras_cachedExtrasOverwrittenByExtenderOld() { - // Sets the flag to old state. - SystemProperties.set("persist.sysui.notification.builder_extras_override", - Boolean.toString(false)); - - Notification.CarExtender extender = new Notification.CarExtender().setColor(1234); - - Notification notification = new Notification.Builder(mContext, "test id") - .extend(extender).build(); - - extender.setColor(5678); - - Notification.Builder.recoverBuilder(mContext, notification).extend(extender).build(); - - Notification.CarExtender recoveredExtender = new Notification.CarExtender(notification); - assertThat(recoveredExtender.getColor()).isEqualTo(1234); - } - @Test @CoreCompatChangeRule.EnableCompatChanges({Notification.WEARABLE_EXTENDER_BACKGROUND_BLOCKED}) public void wearableBackgroundBlockEnabled_wearableBackgroundSet_valueRemainsNull() { diff --git a/core/tests/coretests/src/android/graphics/drawable/IconTest.java b/core/tests/coretests/src/android/graphics/drawable/IconTest.java index 950925f488fd..e0c3b0451a8b 100644 --- a/core/tests/coretests/src/android/graphics/drawable/IconTest.java +++ b/core/tests/coretests/src/android/graphics/drawable/IconTest.java @@ -475,8 +475,8 @@ public class IconTest { final Icon ic = Icon.createWithBitmap(bm); final Drawable drawable = ic.loadDrawable(mContext); - assertThat(drawable.getIntrinsicWidth()).isEqualTo(maxWidth); - assertThat(drawable.getIntrinsicHeight()).isEqualTo(maxHeight); + assertThat(Math.abs(drawable.getIntrinsicWidth() - maxWidth)).isLessThan(2); + assertThat(Math.abs(drawable.getIntrinsicHeight() - maxHeight)).isLessThan(2); } @Test diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java index 0b1b40c8ba8b..bc0ae9f31904 100644 --- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java +++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java @@ -623,6 +623,28 @@ public class ViewFrameRateTest { assertEquals(FRAME_RATE_CATEGORY_HIGH_HINT, mViewRoot.getLastPreferredFrameRateCategory()); } + @Test + @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, + FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY + }) + public void idleDetected() throws Throwable { + waitForFrameRateCategoryToSettle(); + mActivityRule.runOnUiThread(() -> { + mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_HIGH); + mMovingView.setFrameContentVelocity(Float.MAX_VALUE); + mMovingView.invalidate(); + runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_HIGH, + mViewRoot.getLastPreferredFrameRateCategory())); + }); + waitForAfterDraw(); + + // Wait for idle timeout + Thread.sleep(500); + assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE, + mViewRoot.getLastPreferredFrameRateCategory()); + } + private void runAfterDraw(@NonNull Runnable runnable) { Handler handler = new Handler(Looper.getMainLooper()); mAfterDrawLatch = new CountDownLatch(1); diff --git a/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java b/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java new file mode 100644 index 000000000000..bcdac610a49d --- /dev/null +++ b/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util; + +import static junit.framework.Assert.assertEquals; + + +import android.platform.test.annotations.DisabledOnRavenwood; +import android.platform.test.ravenwood.RavenwoodRule; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test for {@link NewlineNormalizer} + * @hide + */ +@DisabledOnRavenwood(blockedBy = NewlineNormalizer.class) +@RunWith(AndroidJUnit4.class) +public class NewlineNormalizerTest { + + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + + @Test + public void testEmptyInput() { + assertEquals("", NewlineNormalizer.normalizeNewlines("")); + } + + @Test + public void testSingleNewline() { + assertEquals("\n", NewlineNormalizer.normalizeNewlines("\n")); + } + + @Test + public void testMultipleConsecutiveNewlines() { + assertEquals("\n", NewlineNormalizer.normalizeNewlines("\n\n\n\n\n")); + } + + @Test + public void testNewlinesWithSpacesAndTabs() { + String input = "Line 1\n \n \t \n\tLine 2"; + // Adjusted expected output to include the tab character + String expected = "Line 1\n\tLine 2"; + assertEquals(expected, NewlineNormalizer.normalizeNewlines(input)); + } + + @Test + public void testMixedNewlineCharacters() { + String input = "Line 1\r\nLine 2\u000BLine 3\fLine 4\u2028Line 5\u2029Line 6"; + String expected = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6"; + assertEquals(expected, NewlineNormalizer.normalizeNewlines(input)); + } +} diff --git a/graphics/java/android/graphics/RecordingCanvas.java b/graphics/java/android/graphics/RecordingCanvas.java index 635e78e674be..cc5b3b94e0fa 100644 --- a/graphics/java/android/graphics/RecordingCanvas.java +++ b/graphics/java/android/graphics/RecordingCanvas.java @@ -40,7 +40,7 @@ public final class RecordingCanvas extends BaseRecordingCanvas { /** @hide */ private static int getPanelFrameSize() { - final int DefaultSize = 100 * 1024 * 1024; // 100 MB; + final int DefaultSize = 150 * 1024 * 1024; // 150 MB; return Math.max(SystemProperties.getInt("ro.hwui.max_texture_allocation_size", DefaultSize), DefaultSize); } @@ -262,7 +262,7 @@ public final class RecordingCanvas extends BaseRecordingCanvas { protected void throwIfCannotDraw(Bitmap bitmap) { super.throwIfCannotDraw(bitmap); int bitmapSize = bitmap.getByteCount(); - if (bitmapSize > MAX_BITMAP_SIZE) { + if (bitmap.getConfig() != Bitmap.Config.HARDWARE && bitmapSize > MAX_BITMAP_SIZE) { throw new RuntimeException( "Canvas: trying to draw too large(" + bitmapSize + "bytes) bitmap."); } diff --git a/ktfmt_includes.txt b/ktfmt_includes.txt index fe4750381fd0..0ac6265ecf27 100644 --- a/ktfmt_includes.txt +++ b/ktfmt_includes.txt @@ -5,8 +5,6 @@ -packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt -packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt -packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt --packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt --packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt -packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt -packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt -packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java b/libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java index d923a46c3b5d..d24164159b2b 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/common/EmptyLifecycleCallbacksAdapter.java @@ -16,6 +16,8 @@ package androidx.window.common; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Activity; import android.app.Application; import android.os.Bundle; @@ -26,30 +28,30 @@ import android.os.Bundle; */ public class EmptyLifecycleCallbacksAdapter implements Application.ActivityLifecycleCallbacks { @Override - public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { } @Override - public void onActivityStarted(Activity activity) { + public void onActivityStarted(@NonNull Activity activity) { } @Override - public void onActivityResumed(Activity activity) { + public void onActivityResumed(@NonNull Activity activity) { } @Override - public void onActivityPaused(Activity activity) { + public void onActivityPaused(@NonNull Activity activity) { } @Override - public void onActivityStopped(Activity activity) { + public void onActivityStopped(@NonNull Activity activity) { } @Override - public void onActivitySaveInstanceState(Activity activity, Bundle outState) { + public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) { } @Override - public void onActivityDestroyed(Activity activity) { + public void onActivityDestroyed(@NonNull Activity activity) { } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java index a0d6fce4c39b..e93b0bf1e69a 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java @@ -35,6 +35,7 @@ import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSI import android.annotation.DimenRes; import android.annotation.Nullable; +import android.app.Activity; import android.app.ActivityThread; import android.content.Context; import android.content.pm.ActivityInfo; @@ -42,6 +43,7 @@ import android.content.res.Configuration; import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.RotateDrawable; import android.hardware.display.DisplayManager; @@ -213,7 +215,11 @@ class DividerPresenter implements View.OnTouchListener { isVerticalSplit, isReversedLayout, parentInfo.getDisplayId(), - isDraggableExpandType + isDraggableExpandType, + getContainerBackgroundColor(topSplitContainer.getPrimaryContainer(), + DEFAULT_PRIMARY_VEIL_COLOR), + getContainerBackgroundColor(topSplitContainer.getSecondaryContainer(), + DEFAULT_SECONDARY_VEIL_COLOR) )); } } @@ -242,6 +248,27 @@ class DividerPresenter implements View.OnTouchListener { } /** + * Returns the window background color of the top activity in the container if set, or the + * default color if the background color of the top activity is unavailable. + */ + @VisibleForTesting + @NonNull + static Color getContainerBackgroundColor( + @NonNull TaskFragmentContainer container, @NonNull Color defaultColor) { + final Activity activity = container.getTopNonFinishingActivity(); + if (activity == null || !activity.isResumed()) { + // This can happen when the top activity in the container is from a different process. + return defaultColor; + } + + final Drawable drawable = activity.getWindow().getDecorView().getBackground(); + if (drawable instanceof ColorDrawable colorDrawable) { + return Color.valueOf(colorDrawable.getColor()); + } + return defaultColor; + } + + /** * Creates a decor surface for the TaskFragment if no decor surface exists, or changes the owner * of the existing decor surface to be the specified TaskFragment. * @@ -439,10 +466,6 @@ class DividerPresenter implements View.OnTouchListener { } if (dividerAttributes.getDividerType() == DividerAttributes.DIVIDER_TYPE_DRAGGABLE) { - // Draggable divider width must be larger than the drag handle size. - widthDp = Math.max(widthDp, - getDimensionDp(R.dimen.activity_embedding_divider_touch_target_width)); - // Update minRatio and maxRatio only when it is a draggable divider. if (minRatio == RATIO_SYSTEM_DEFAULT) { minRatio = DEFAULT_MIN_RATIO; @@ -800,6 +823,8 @@ class DividerPresenter implements View.OnTouchListener { private final int mDisplayId; private final boolean mIsReversedLayout; private final boolean mIsDraggableExpandType; + private final Color mPrimaryVeilColor; + private final Color mSecondaryVeilColor; @VisibleForTesting Properties( @@ -810,7 +835,9 @@ class DividerPresenter implements View.OnTouchListener { boolean isVerticalSplit, boolean isReversedLayout, int displayId, - boolean isDraggableExpandType) { + boolean isDraggableExpandType, + @NonNull Color primaryVeilColor, + @NonNull Color secondaryVeilColor) { mConfiguration = configuration; mDividerAttributes = dividerAttributes; mDecorSurface = decorSurface; @@ -819,6 +846,8 @@ class DividerPresenter implements View.OnTouchListener { mIsReversedLayout = isReversedLayout; mDisplayId = displayId; mIsDraggableExpandType = isDraggableExpandType; + mPrimaryVeilColor = primaryVeilColor; + mSecondaryVeilColor = secondaryVeilColor; } /** @@ -840,7 +869,9 @@ class DividerPresenter implements View.OnTouchListener { && a.mIsVerticalSplit == b.mIsVerticalSplit && a.mDisplayId == b.mDisplayId && a.mIsReversedLayout == b.mIsReversedLayout - && a.mIsDraggableExpandType == b.mIsDraggableExpandType; + && a.mIsDraggableExpandType == b.mIsDraggableExpandType + && a.mPrimaryVeilColor.equals(b.mPrimaryVeilColor) + && a.mSecondaryVeilColor.equals(b.mSecondaryVeilColor); } private static boolean areSameSurfaces( @@ -877,17 +908,21 @@ class DividerPresenter implements View.OnTouchListener { @NonNull private final FrameLayout mDividerLayout; @NonNull + private final View mDividerLine; + private View mDragHandle; + @NonNull private final View.OnTouchListener mListener; @NonNull private Properties mProperties; private int mDividerWidthPx; + private int mHandleWidthPx; @Nullable private SurfaceControl mPrimaryVeil; @Nullable private SurfaceControl mSecondaryVeil; private boolean mIsDragging; private int mDividerPosition; - private View mDragHandle; + private int mDividerSurfaceWidthPx; private Renderer(@NonNull Properties properties, @NonNull View.OnTouchListener listener) { mProperties = properties; @@ -905,6 +940,7 @@ class DividerPresenter implements View.OnTouchListener { context, displayManager.getDisplay(mProperties.mDisplayId), mWindowlessWindowManager, "DividerContainer"); mDividerLayout = new FrameLayout(context); + mDividerLine = new View(context); update(); } @@ -921,6 +957,17 @@ class DividerPresenter implements View.OnTouchListener { mDividerWidthPx = getDividerWidthPx(mProperties.mDividerAttributes); mDividerPosition = mProperties.mInitialDividerPosition; mWindowlessWindowManager.setConfiguration(mProperties.mConfiguration); + + if (mProperties.mDividerAttributes.getDividerType() + == DividerAttributes.DIVIDER_TYPE_DRAGGABLE) { + // TODO(b/329193115) support divider on secondary display + final Context context = ActivityThread.currentActivityThread().getApplication(); + mHandleWidthPx = context.getResources().getDimensionPixelSize( + R.dimen.activity_embedding_divider_touch_target_width); + } else { + mHandleWidthPx = 0; + } + // TODO handle synchronization between surface transactions and WCT. final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); updateSurface(t); @@ -950,13 +997,45 @@ class DividerPresenter implements View.OnTouchListener { */ private void updateSurface(@NonNull SurfaceControl.Transaction t) { final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds(); + + int dividerSurfacePosition; + if (mProperties.mDividerAttributes.getDividerType() + == DividerAttributes.DIVIDER_TYPE_DRAGGABLE) { + // When the divider drag handle width is larger than the divider width, the position + // of the divider surface is adjusted so that it is large enough to host both the + // divider line and the divider drag handle. + mDividerSurfaceWidthPx = Math.max(mDividerWidthPx, mHandleWidthPx); + dividerSurfacePosition = + mProperties.mIsReversedLayout + ? mDividerPosition + : mDividerPosition + mDividerWidthPx - mDividerSurfaceWidthPx; + dividerSurfacePosition = Math.clamp(dividerSurfacePosition, 0, + mProperties.mIsVerticalSplit ? taskBounds.width() : taskBounds.height()); + } else { + mDividerSurfaceWidthPx = mDividerWidthPx; + dividerSurfacePosition = mDividerPosition; + } + if (mProperties.mIsVerticalSplit) { - t.setPosition(mDividerSurface, mDividerPosition, 0.0f); - t.setWindowCrop(mDividerSurface, mDividerWidthPx, taskBounds.height()); + t.setPosition(mDividerSurface, dividerSurfacePosition, 0.0f); + t.setWindowCrop(mDividerSurface, mDividerSurfaceWidthPx, taskBounds.height()); } else { - t.setPosition(mDividerSurface, 0.0f, mDividerPosition); - t.setWindowCrop(mDividerSurface, taskBounds.width(), mDividerWidthPx); + t.setPosition(mDividerSurface, 0.0f, dividerSurfacePosition); + t.setWindowCrop(mDividerSurface, taskBounds.width(), mDividerSurfaceWidthPx); } + + // Update divider line position in the surface + if (!mProperties.mIsReversedLayout) { + final int offset = mDividerPosition - dividerSurfacePosition; + mDividerLine.setX(mProperties.mIsVerticalSplit ? offset : 0); + mDividerLine.setY(mProperties.mIsVerticalSplit ? 0 : offset); + } else { + // For reversed layout, the divider line is always at the start of the divider + // surface. + mDividerLine.setX(0); + mDividerLine.setY(0); + } + if (mIsDragging) { updateVeils(t); } @@ -971,14 +1050,14 @@ class DividerPresenter implements View.OnTouchListener { final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds(); final WindowManager.LayoutParams lp = mProperties.mIsVerticalSplit ? new WindowManager.LayoutParams( - mDividerWidthPx, + mDividerSurfaceWidthPx, taskBounds.height(), TYPE_APPLICATION_PANEL, FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_SLIPPERY, PixelFormat.TRANSLUCENT) : new WindowManager.LayoutParams( taskBounds.width(), - mDividerWidthPx, + mDividerSurfaceWidthPx, TYPE_APPLICATION_PANEL, FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL | FLAG_SLIPPERY, PixelFormat.TRANSLUCENT); @@ -995,12 +1074,19 @@ class DividerPresenter implements View.OnTouchListener { */ private void updateDivider(@NonNull SurfaceControl.Transaction t) { mDividerLayout.removeAllViews(); - if (mProperties.mIsDraggableExpandType) { + mDividerLayout.addView(mDividerLine); + if (mProperties.mIsDraggableExpandType && !mIsDragging) { // If a container is fully expanded, the divider overlays on the expanded container. - mDividerLayout.setBackgroundColor(Color.TRANSPARENT); + mDividerLine.setBackgroundColor(Color.TRANSPARENT); } else { - mDividerLayout.setBackgroundColor(mProperties.mDividerAttributes.getDividerColor()); + mDividerLine.setBackgroundColor(mProperties.mDividerAttributes.getDividerColor()); } + final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds(); + mDividerLine.setLayoutParams( + mProperties.mIsVerticalSplit + ? new FrameLayout.LayoutParams(mDividerWidthPx, taskBounds.height()) + : new FrameLayout.LayoutParams(taskBounds.width(), mDividerWidthPx) + ); if (mProperties.mDividerAttributes.getDividerType() == DividerAttributes.DIVIDER_TYPE_DRAGGABLE) { createVeils(); @@ -1027,7 +1113,7 @@ class DividerPresenter implements View.OnTouchListener { R.dimen.activity_embedding_divider_touch_target_width)); params.gravity = Gravity.CENTER; button.setLayoutParams(params); - button.setBackgroundColor(R.color.transparent); + button.setBackgroundColor(Color.TRANSPARENT); final Drawable handle = context.getResources().getDrawable( R.drawable.activity_embedding_divider_handle, context.getTheme()); @@ -1087,8 +1173,8 @@ class DividerPresenter implements View.OnTouchListener { } private void showVeils(@NonNull SurfaceControl.Transaction t) { - t.setColor(mPrimaryVeil, colorToFloatArray(DEFAULT_PRIMARY_VEIL_COLOR)) - .setColor(mSecondaryVeil, colorToFloatArray(DEFAULT_SECONDARY_VEIL_COLOR)) + t.setColor(mPrimaryVeil, colorToFloatArray(mProperties.mPrimaryVeilColor)) + .setColor(mSecondaryVeil, colorToFloatArray(mProperties.mSecondaryVeilColor)) .setLayer(mDividerSurface, DIVIDER_LAYER) .setLayer(mPrimaryVeil, VEIL_LAYER) .setLayer(mSecondaryVeil, VEIL_LAYER) 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 14388a6d42ff..5b0e6b9c49a1 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -855,7 +855,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (!parentInfo.isVisible()) { // Only making the TaskContainer invisible and drops the other info, and perform the // update when the next time the Task becomes visible. - taskContainer.setIsVisible(false); + if (taskContainer.isVisible()) { + taskContainer.setInvisible(); + } return; } @@ -3228,10 +3230,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @NonNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer) { final DividerPresenter dividerPresenter = mDividerPresenters.get(taskContainer.getTaskId()); final TaskFragmentParentInfo parentInfo = taskContainer.getTaskFragmentParentInfo(); - if (parentInfo != null) { - dividerPresenter.updateDivider( - wct, parentInfo, taskContainer.getTopNonFinishingSplitContainer()); - } + dividerPresenter.updateDivider( + wct, parentInfo, taskContainer.getTopNonFinishingSplitContainer()); } @Override diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index a68373832a14..c708da97d908 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -78,16 +78,7 @@ class TaskContainer { private TaskFragmentContainer mAlwaysOnTopOverlayContainer; @NonNull - private final Configuration mConfiguration; - - private int mDisplayId; - - private boolean mIsVisible; - - private boolean mHasDirectActivity; - - @Nullable - private TaskFragmentParentInfo mTaskFragmentParentInfo; + private TaskFragmentParentInfo mInfo; /** * TaskFragments that the organizer has requested to be closed. They should be removed when @@ -131,12 +122,14 @@ class TaskContainer { mTaskId = taskId; final TaskProperties taskProperties = TaskProperties .getTaskPropertiesFromActivity(activityInTask); - mConfiguration = taskProperties.getConfiguration(); - mDisplayId = taskProperties.getDisplayId(); - // Note that it is always called when there's a new Activity is started, which implies - // the host task is visible and has an activity in the task. - mIsVisible = true; - mHasDirectActivity = true; + mInfo = new TaskFragmentParentInfo( + taskProperties.getConfiguration(), + taskProperties.getDisplayId(), + // Note that it is always called when there's a new Activity is started, which + // implies the host task is visible and has an activity in the task. + true /* visible */, + true /* hasDirectActivity */, + null /* decorSurface */); } int getTaskId() { @@ -144,43 +137,39 @@ class TaskContainer { } int getDisplayId() { - return mDisplayId; + return mInfo.getDisplayId(); } boolean isVisible() { - return mIsVisible; + return mInfo.isVisible(); } - void setIsVisible(boolean visible) { - mIsVisible = visible; + void setInvisible() { + mInfo = new TaskFragmentParentInfo(mInfo.getConfiguration(), mInfo.getDisplayId(), + false /* visible */, mInfo.hasDirectActivity(), mInfo.getDecorSurface()); } boolean hasDirectActivity() { - return mHasDirectActivity; + return mInfo.hasDirectActivity(); } @NonNull Rect getBounds() { - return mConfiguration.windowConfiguration.getBounds(); + return mInfo.getConfiguration().windowConfiguration.getBounds(); } @NonNull TaskProperties getTaskProperties() { - return new TaskProperties(mDisplayId, mConfiguration); + return new TaskProperties(mInfo.getDisplayId(), mInfo.getConfiguration()); } void updateTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) { - // TODO(b/293654166): cache the TaskFragmentParentInfo and remove these fields. - mConfiguration.setTo(info.getConfiguration()); - mDisplayId = info.getDisplayId(); - mIsVisible = info.isVisible(); - mHasDirectActivity = info.hasDirectActivity(); - mTaskFragmentParentInfo = info; + mInfo = info; } - @Nullable + @NonNull TaskFragmentParentInfo getTaskFragmentParentInfo() { - return mTaskFragmentParentInfo; + return mInfo; } /** @@ -196,8 +185,8 @@ class TaskContainer { // If the task properties equals regardless of starting position, don't // need to update the container. - return mConfiguration.diffPublicOnly(configuration) != 0 - || mDisplayId != info.getDisplayId(); + return mInfo.getConfiguration().diffPublicOnly(configuration) != 0 + || mInfo.getDisplayId() != info.getDisplayId(); } /** @@ -224,7 +213,7 @@ class TaskContainer { } boolean isInPictureInPicture() { - return isInPictureInPicture(mConfiguration); + return isInPictureInPicture(mInfo.getConfiguration()); } private static boolean isInPictureInPicture(@NonNull Configuration configuration) { @@ -237,7 +226,7 @@ class TaskContainer { @WindowingMode private int getWindowingMode() { - return mConfiguration.windowConfiguration.getWindowingMode(); + return mInfo.getConfiguration().windowConfiguration.getWindowingMode(); } /** Whether there is any {@link TaskFragmentContainer} below this Task. */ diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java index 56c3bce87d6e..339908a3a9a4 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java @@ -16,16 +16,10 @@ package androidx.window.sidecar; -import static android.view.Display.DEFAULT_DISPLAY; - -import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation; -import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect; - +import android.annotation.Nullable; import android.app.Activity; -import android.app.ActivityThread; import android.app.Application; import android.content.Context; -import android.graphics.Rect; import android.hardware.devicestate.DeviceStateManager; import android.os.Bundle; import android.os.IBinder; @@ -38,7 +32,6 @@ import androidx.window.common.RawFoldingFeatureProducer; import androidx.window.util.BaseDataProducer; import java.util.ArrayList; -import java.util.Collections; import java.util.List; /** @@ -76,64 +69,13 @@ class SampleSidecarImpl extends StubSidecar { @NonNull @Override public SidecarDeviceState getDeviceState() { - SidecarDeviceState deviceState = new SidecarDeviceState(); - deviceState.posture = deviceStateFromFeature(); - return deviceState; - } - - private int deviceStateFromFeature() { - for (int i = 0; i < mStoredFeatures.size(); i++) { - CommonFoldingFeature feature = mStoredFeatures.get(i); - final int state = feature.getState(); - switch (state) { - case CommonFoldingFeature.COMMON_STATE_FLAT: - return SidecarDeviceState.POSTURE_OPENED; - case CommonFoldingFeature.COMMON_STATE_HALF_OPENED: - return SidecarDeviceState.POSTURE_HALF_OPENED; - case CommonFoldingFeature.COMMON_STATE_UNKNOWN: - return SidecarDeviceState.POSTURE_UNKNOWN; - } - } - return SidecarDeviceState.POSTURE_UNKNOWN; + return SidecarHelper.calculateDeviceState(mStoredFeatures); } @NonNull @Override public SidecarWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) { - Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken); - SidecarWindowLayoutInfo windowLayoutInfo = new SidecarWindowLayoutInfo(); - if (activity == null) { - return windowLayoutInfo; - } - windowLayoutInfo.displayFeatures = getDisplayFeatures(activity); - return windowLayoutInfo; - } - - private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) { - int displayId = activity.getDisplay().getDisplayId(); - if (displayId != DEFAULT_DISPLAY) { - return Collections.emptyList(); - } - - if (activity.isInMultiWindowMode()) { - // It is recommended not to report any display features in multi-window mode, since it - // won't be possible to synchronize the display feature positions with window movement. - return Collections.emptyList(); - } - - List<SidecarDisplayFeature> features = new ArrayList<>(); - final int rotation = activity.getResources().getConfiguration().windowConfiguration - .getDisplayRotation(); - for (CommonFoldingFeature baseFeature : mStoredFeatures) { - SidecarDisplayFeature feature = new SidecarDisplayFeature(); - Rect featureRect = baseFeature.getRect(); - rotateRectToDisplayRotation(displayId, rotation, featureRect); - transformToWindowSpaceRect(activity, featureRect); - feature.setRect(featureRect); - feature.setType(baseFeature.getType()); - features.add(feature); - } - return Collections.unmodifiableList(features); + return SidecarHelper.calculateWindowLayoutInfo(windowToken, mStoredFeatures); } @Override @@ -145,13 +87,14 @@ class SampleSidecarImpl extends StubSidecar { private final class NotifyOnConfigurationChanged extends EmptyLifecycleCallbacksAdapter { @Override - public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + public void onActivityCreated(@NonNull Activity activity, + @Nullable Bundle savedInstanceState) { super.onActivityCreated(activity, savedInstanceState); onDisplayFeaturesChangedForActivity(activity); } @Override - public void onActivityConfigurationChanged(Activity activity) { + public void onActivityConfigurationChanged(@NonNull Activity activity) { super.onActivityConfigurationChanged(activity); onDisplayFeaturesChangedForActivity(activity); } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java new file mode 100644 index 000000000000..bb6ab47b144d --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.window.sidecar; + +import static android.view.Display.DEFAULT_DISPLAY; + +import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation; +import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect; + +import android.annotation.NonNull; +import android.app.Activity; +import android.app.ActivityThread; +import android.graphics.Rect; +import android.os.IBinder; + +import androidx.window.common.CommonFoldingFeature; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A utility class for transforming between Sidecar and Extensions features. + */ +class SidecarHelper { + + private SidecarHelper() {} + + /** + * Returns the {@link SidecarDeviceState} posture that is calculated for the first fold in + * the feature list. Sidecar devices only have one fold so we only pick the first one to + * determine the state. + * @param featureList the {@link CommonFoldingFeature} that are currently active. + * @return the {@link SidecarDeviceState} calculated from the {@link List} of + * {@link CommonFoldingFeature}. + */ + @SuppressWarnings("deprecation") + private static int deviceStateFromFeatureList(@NonNull List<CommonFoldingFeature> featureList) { + for (int i = 0; i < featureList.size(); i++) { + final CommonFoldingFeature feature = featureList.get(i); + final int state = feature.getState(); + switch (state) { + case CommonFoldingFeature.COMMON_STATE_FLAT: + return SidecarDeviceState.POSTURE_OPENED; + case CommonFoldingFeature.COMMON_STATE_HALF_OPENED: + return SidecarDeviceState.POSTURE_HALF_OPENED; + case CommonFoldingFeature.COMMON_STATE_UNKNOWN: + return SidecarDeviceState.POSTURE_UNKNOWN; + case CommonFoldingFeature.COMMON_STATE_NO_FOLDING_FEATURES: + return SidecarDeviceState.POSTURE_UNKNOWN; + case CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE: + return SidecarDeviceState.POSTURE_UNKNOWN; + } + } + return SidecarDeviceState.POSTURE_UNKNOWN; + } + + /** + * Returns a {@link SidecarDeviceState} calculated from a {@link List} of + * {@link CommonFoldingFeature}s. + */ + @SuppressWarnings("deprecation") + static SidecarDeviceState calculateDeviceState( + @NonNull List<CommonFoldingFeature> featureList) { + final SidecarDeviceState deviceState = new SidecarDeviceState(); + deviceState.posture = deviceStateFromFeatureList(featureList); + return deviceState; + } + + @SuppressWarnings("deprecation") + private static List<SidecarDisplayFeature> calculateDisplayFeatures( + @NonNull Activity activity, + @NonNull List<CommonFoldingFeature> featureList + ) { + final int displayId = activity.getDisplay().getDisplayId(); + if (displayId != DEFAULT_DISPLAY) { + return Collections.emptyList(); + } + + if (activity.isInMultiWindowMode()) { + // It is recommended not to report any display features in multi-window mode, since it + // won't be possible to synchronize the display feature positions with window movement. + return Collections.emptyList(); + } + + final List<SidecarDisplayFeature> features = new ArrayList<>(); + final int rotation = activity.getResources().getConfiguration().windowConfiguration + .getDisplayRotation(); + for (CommonFoldingFeature baseFeature : featureList) { + final SidecarDisplayFeature feature = new SidecarDisplayFeature(); + final Rect featureRect = baseFeature.getRect(); + rotateRectToDisplayRotation(displayId, rotation, featureRect); + transformToWindowSpaceRect(activity, featureRect); + feature.setRect(featureRect); + feature.setType(baseFeature.getType()); + features.add(feature); + } + return Collections.unmodifiableList(features); + } + + /** + * Returns a {@link SidecarWindowLayoutInfo} calculated from the {@link List} of + * {@link CommonFoldingFeature}. + */ + @SuppressWarnings("deprecation") + static SidecarWindowLayoutInfo calculateWindowLayoutInfo(@NonNull IBinder windowToken, + @NonNull List<CommonFoldingFeature> featureList) { + final Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken); + final SidecarWindowLayoutInfo windowLayoutInfo = new SidecarWindowLayoutInfo(); + if (activity == null) { + return windowLayoutInfo; + } + windowLayoutInfo.displayFeatures = calculateDisplayFeatures(activity, featureList); + return windowLayoutInfo; + } +} diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java index 8aca92e89e6b..ad913c98482c 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java @@ -35,8 +35,11 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.Activity; import android.content.res.Configuration; +import android.graphics.Color; import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; import android.os.Binder; import android.os.IBinder; import android.platform.test.annotations.Presubmit; @@ -44,6 +47,8 @@ import android.platform.test.flag.junit.SetFlagsRule; import android.view.Display; import android.view.MotionEvent; import android.view.SurfaceControl; +import android.view.View; +import android.view.Window; import android.window.TaskFragmentOperation; import android.window.TaskFragmentParentInfo; import android.window.WindowContainerTransaction; @@ -152,7 +157,10 @@ public class DividerPresenterTest { true /* isVerticalSplit */, false /* isReversedLayout */, Display.DEFAULT_DISPLAY, - false /* isDraggableExpandType */); + false /* isDraggableExpandType */, + Color.valueOf(Color.BLACK), /* primaryVeilColor */ + Color.valueOf(Color.GRAY) /* secondaryVeilColor */ + ); mDividerPresenter = new DividerPresenter( MOCK_TASK_ID, mDragEventCallback, mock(Executor.class)); @@ -604,6 +612,38 @@ public class DividerPresenterTest { 0.0001 /* delta */); } + @Test + public void testGetContainerBackgroundColor() { + final Color defaultColor = Color.valueOf(Color.RED); + final Color activityBackgroundColor = Color.valueOf(Color.BLUE); + final TaskFragmentContainer container = mock(TaskFragmentContainer.class); + final Activity activity = mock(Activity.class); + final Window window = mock(Window.class); + final View decorView = mock(View.class); + final ColorDrawable backgroundDrawable = + new ColorDrawable(activityBackgroundColor.toArgb()); + when(activity.getWindow()).thenReturn(window); + when(window.getDecorView()).thenReturn(decorView); + when(decorView.getBackground()).thenReturn(backgroundDrawable); + + // When the top non-finishing activity returns null, the default color should be returned. + when(container.getTopNonFinishingActivity()).thenReturn(null); + assertEquals(defaultColor, + DividerPresenter.getContainerBackgroundColor(container, defaultColor)); + + // When the top non-finishing activity is not resumed, the default color should be returned. + when(container.getTopNonFinishingActivity()).thenReturn(activity); + when(activity.isResumed()).thenReturn(false); + assertEquals(defaultColor, + DividerPresenter.getContainerBackgroundColor(container, defaultColor)); + + // When the top non-finishing activity is resumed, its background color should be returned. + when(container.getTopNonFinishingActivity()).thenReturn(activity); + when(activity.isResumed()).thenReturn(true); + assertEquals(activityBackgroundColor, + DividerPresenter.getContainerBackgroundColor(container, defaultColor)); + } + private TaskFragmentContainer createMockTaskFragmentContainer( @NonNull IBinder token, @NonNull Rect bounds) { final TaskFragmentContainer container = mock(TaskFragmentContainer.class); diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt index 611013365e7d..076414132e27 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt @@ -31,7 +31,6 @@ import com.android.internal.protolog.common.ProtoLog import com.android.wm.shell.R import com.android.wm.shell.bubbles.BubblePositioner import com.android.wm.shell.bubbles.DeviceConfig -import com.android.wm.shell.bubbles.bar.BubbleExpandedViewPinController.Companion.DROP_TARGET_SCALE import com.android.wm.shell.common.bubbles.BaseBubblePinController import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_IN_DURATION import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_OUT_DURATION @@ -248,15 +247,8 @@ class BubbleExpandedViewPinControllerTest { private val dropTargetView: View? get() = container.findViewById(R.id.bubble_bar_drop_target) - private fun getExpectedDropTargetBounds(onLeft: Boolean): Rect { - val rect = Rect() - positioner.getBubbleBarExpandedViewBounds(onLeft, false /* isOveflowExpanded */, rect) - // Scale the rect to expected size, but keep the center point the same - val centerX = rect.centerX() - val centerY = rect.centerY() - rect.scale(DROP_TARGET_SCALE) - rect.offset(centerX - rect.centerX(), centerY - rect.centerY()) - return rect + private fun getExpectedDropTargetBounds(onLeft: Boolean): Rect = Rect().also { + positioner.getBubbleBarExpandedViewBounds(onLeft, false /* isOveflowExpanded */, it) } private fun runOnMainSync(runnable: Runnable) { diff --git a/libs/WindowManager/Shell/res/drawable/bubble_drop_target_background.xml b/libs/WindowManager/Shell/res/drawable/bubble_drop_target_background.xml index 9dcde3b54421..b928a0b20764 100644 --- a/libs/WindowManager/Shell/res/drawable/bubble_drop_target_background.xml +++ b/libs/WindowManager/Shell/res/drawable/bubble_drop_target_background.xml @@ -13,12 +13,14 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<shape xmlns:android="http://schemas.android.com/apk/res/android" +<inset xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:shape="rectangle"> - <corners android:radius="@dimen/bubble_bar_expanded_view_corner_radius" /> - <solid android:color="@color/bubble_drop_target_background_color" /> - <stroke - android:width="1dp" - android:color="?androidprv:attr/materialColorPrimaryContainer" /> -</shape> + android:inset="@dimen/bubble_bar_expanded_view_drop_target_padding"> + <shape android:shape="rectangle"> + <corners android:radius="@dimen/bubble_bar_expanded_view_drop_target_corner" /> + <solid android:color="@color/bubble_drop_target_background_color" /> + <stroke + android:width="1dp" + android:color="?androidprv:attr/materialColorPrimaryContainer" /> + </shape> +</inset> diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml index 3aecf5f53cf0..3492f136c4f9 100644 --- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml @@ -62,9 +62,9 @@ <string name="bubble_content_description_single" msgid="8495748092720065813">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de <xliff:g id="APP_NAME">%2$s</xliff:g>"</string> <string name="bubble_content_description_stack" msgid="8071515017164630429">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de <xliff:g id="APP_NAME">%2$s</xliff:g> et <xliff:g id="BUBBLE_COUNT">%3$d</xliff:g> autres"</string> <string name="bubble_accessibility_action_move_top_left" msgid="2644118920500782758">"Déplacer dans coin sup. gauche"</string> - <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Déplacer dans coin sup. droit"</string> - <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Déplacer dans coin inf. gauche"</string> - <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Déplacer dans coin inf. droit"</string> + <string name="bubble_accessibility_action_move_top_right" msgid="5864594920870245525">"Déplacer en haut à droite"</string> + <string name="bubble_accessibility_action_move_bottom_left" msgid="850271002773745634">"Déplacer en bas à gauche"</string> + <string name="bubble_accessibility_action_move_bottom_right" msgid="2107626346109206352">"Déplacer en bas à droite"</string> <string name="bubble_accessibility_announce_expand" msgid="5388792092888203776">"développer <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string> <string name="bubble_accessibility_announce_collapse" msgid="3178806224494537097">"réduire <xliff:g id="BUBBLE_TITLE">%1$s</xliff:g>"</string> <string name="bubbles_app_settings" msgid="3617224938701566416">"Paramètres <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string> diff --git a/libs/WindowManager/Shell/res/values-ky/strings.xml b/libs/WindowManager/Shell/res/values-ky/strings.xml index 7b7779d85d78..302c0071a73a 100644 --- a/libs/WindowManager/Shell/res/values-ky/strings.xml +++ b/libs/WindowManager/Shell/res/values-ky/strings.xml @@ -71,7 +71,7 @@ <string name="bubble_dismiss_text" msgid="8816558050659478158">"Калкып чыкма билдирмени жабуу"</string> <string name="bubbles_dont_bubble_conversation" msgid="310000317885712693">"Жазышууда калкып чыкма билдирмелер көрүнбөсүн"</string> <string name="bubbles_user_education_title" msgid="2112319053732691899">"Калкып чыкма билдирмелер аркылуу маектешүү"</string> - <string name="bubbles_user_education_description" msgid="4215862563054175407">"Жаңы жазышуулар калкыма сүрөтчөлөр же калкып чыкма билдирмелер түрүндө көрүнөт. Калкып чыкма билдирмелерди ачуу үчүн таптап коюңуз. Жылдыруу үчүн сүйрөңүз."</string> + <string name="bubbles_user_education_description" msgid="4215862563054175407">"Жаңы жазышуулар калкыма сүрөтчөлөр же калкып чыкма билдирмелер түрүндө көрүнөт. Калкып чыкма билдирмелерди ачуу үчүн тийип коюңуз. Жылдыруу үчүн сүйрөңүз."</string> <string name="bubbles_user_education_manage_title" msgid="7042699946735628035">"Калкып чыкма билдирмелерди каалаган убакта көзөмөлдөңүз"</string> <string name="bubbles_user_education_manage" msgid="3460756219946517198">"Бул колдонмодогу калкып чыкма билдирмелерди өчүрүү үчүн \"Башкарууну\" басыңыз"</string> <string name="bubbles_user_education_got_it" msgid="3382046149225428296">"Түшүндүм"</string> diff --git a/libs/WindowManager/Shell/res/values-mn/strings.xml b/libs/WindowManager/Shell/res/values-mn/strings.xml index 4a9fab92f11f..5e43506ab621 100644 --- a/libs/WindowManager/Shell/res/values-mn/strings.xml +++ b/libs/WindowManager/Shell/res/values-mn/strings.xml @@ -53,7 +53,7 @@ <string name="accessibility_split_top" msgid="2789329702027147146">"Дээд талд хуваах"</string> <string name="accessibility_split_bottom" msgid="8694551025220868191">"Доод талд хуваах"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Нэг гарын горимыг ашиглаж байна"</string> - <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Гарахын тулд дэлгэцийн доод хэсгээс дээш шударч эсвэл апп дээр хүссэн газраа товшино уу"</string> + <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Гарахын тулд дэлгэцийн доод хэсгээс дээш шударч эсвэл аппын дээр хүссэн газраа товшино уу"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Нэг гарын горимыг эхлүүлэх"</string> <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Нэг гарын горимоос гарах"</string> <string name="bubbles_settings_button_description" msgid="1301286017420516912">"<xliff:g id="APP_NAME">%1$s</xliff:g>-н бөмбөлгүүдийн тохиргоо"</string> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index f532f96ccfc9..8d24c161e3e4 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -274,6 +274,9 @@ <dimen name="bubble_bar_expanded_view_corner_radius">16dp</dimen> <!-- Corner radius for expanded view while it is being dragged --> <dimen name="bubble_bar_expanded_view_corner_radius_dragged">28dp</dimen> + <!-- Corner radius for expanded view drop target --> + <dimen name="bubble_bar_expanded_view_drop_target_corner">28dp</dimen> + <dimen name="bubble_bar_expanded_view_drop_target_padding">24dp</dimen> <!-- Width of the box around bottom center of the screen where drag only leads to dismiss --> <dimen name="bubble_bar_dismiss_zone_width">192dp</dimen> <!-- Height of the box around bottom center of the screen where drag only leads to dismiss --> 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 87aac0b55e35..9e6c5fbf9f5e 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 @@ -170,6 +170,8 @@ public class BubbleController implements ConfigurationChangeListener, * the pointer might need to be updated. */ void bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer); + /** Called when the bubble overflow empty state changes, used to show/hide the overflow. */ + void bubbleOverflowChanged(boolean hasBubbles); } private final Context mContext; @@ -1169,6 +1171,15 @@ public class BubbleController implements ConfigurationChangeListener, */ public void startBubbleDrag(String bubbleKey) { onBubbleDrag(bubbleKey, true /* isBeingDragged */); + if (mBubbleStateListener != null) { + boolean overflow = BubbleOverflow.KEY.equals(bubbleKey); + Rect rect = new Rect(); + mBubblePositioner.getBubbleBarExpandedViewBounds(mBubblePositioner.isBubbleBarOnLeft(), + overflow, rect); + BubbleBarUpdate update = new BubbleBarUpdate(); + update.expandedViewDropTargetSize = new Point(rect.width(), rect.height()); + mBubbleStateListener.onBubbleStateChange(update); + } } /** @@ -1401,7 +1412,7 @@ public class BubbleController implements ConfigurationChangeListener, Bubble b = mBubbleData.getOverflowBubbleWithKey(appBubbleKey); if (b != null) { // It's in the overflow, so remove it & reinflate - mBubbleData.removeOverflowBubble(b); + mBubbleData.dismissBubbleWithKey(appBubbleKey, Bubbles.DISMISS_NOTIF_CANCEL); } else { // App bubble does not exist, lets add and expand it b = Bubble.createAppBubble(intent, user, icon, mMainExecutor); @@ -1844,6 +1855,11 @@ public class BubbleController implements ConfigurationChangeListener, } } + + @Override + public void bubbleOverflowChanged(boolean hasBubbles) { + // TODO (b/334175587): tell stack view to hide / show the overflow + } }; /** When bubbles are in the bubble bar, this will be used to notify bubble bar views. */ @@ -1876,6 +1892,11 @@ public class BubbleController implements ConfigurationChangeListener, } @Override + public void bubbleOverflowChanged(boolean hasBubbles) { + // Nothing to do for our views, handled by launcher / in the bubble bar. + } + + @Override public void suppressionChanged(Bubble bubble, boolean isSuppressed) { if (mLayerView != null) { // TODO (b/273316505) handle suppression changes, although might not need to @@ -1914,7 +1935,7 @@ public class BubbleController implements ConfigurationChangeListener, ProtoLog.d(WM_SHELL_BUBBLES, "mBubbleDataListener#applyUpdate:" + " added=%s removed=%b updated=%s orderChanged=%b expansionChanged=%b" + " expanded=%b selectionChanged=%b selected=%s" - + " suppressed=%s unsupressed=%s shouldShowEducation=%b", + + " suppressed=%s unsupressed=%s shouldShowEducation=%b showOverflowChanged=%b", update.addedBubble != null ? update.addedBubble.getKey() : "null", !update.removedBubbles.isEmpty(), update.updatedBubble != null ? update.updatedBubble.getKey() : "null", @@ -1923,13 +1944,17 @@ public class BubbleController implements ConfigurationChangeListener, update.selectedBubble != null ? update.selectedBubble.getKey() : "null", update.suppressedBubble != null ? update.suppressedBubble.getKey() : "null", update.unsuppressedBubble != null ? update.unsuppressedBubble.getKey() : "null", - update.shouldShowEducation); + update.shouldShowEducation, update.showOverflowChanged); ensureBubbleViewsAndWindowCreated(); // Lazy load overflow bubbles from disk loadOverflowBubblesFromDisk(); + if (update.showOverflowChanged) { + mBubbleViewCallback.bubbleOverflowChanged(!update.overflowBubbles.isEmpty()); + } + // If bubbles in the overflow have a dot, make sure the overflow shows a dot updateOverflowButtonDot(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index ae3d0c559014..ea30af5c3d5a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -77,6 +77,7 @@ public class BubbleData { boolean suppressedSummaryChanged; boolean expanded; boolean shouldShowEducation; + boolean showOverflowChanged; @Nullable BubbleViewProvider selectedBubble; @Nullable Bubble addedBubble; @Nullable Bubble updatedBubble; @@ -109,7 +110,8 @@ public class BubbleData { || suppressedBubble != null || unsuppressedBubble != null || suppressedSummaryChanged - || suppressedSummaryGroup != null; + || suppressedSummaryGroup != null + || showOverflowChanged; } void bubbleRemoved(Bubble bubbleToRemove, @DismissReason int reason) { @@ -157,6 +159,8 @@ public class BubbleData { bubbleBarUpdate.bubbleKeysInOrder.add(bubbles.get(i).getKey()); } } + bubbleBarUpdate.showOverflowChanged = showOverflowChanged; + bubbleBarUpdate.showOverflow = !overflowBubbles.isEmpty(); return bubbleBarUpdate; } @@ -410,6 +414,9 @@ public class BubbleData { if (bubbleToReturn != null) { // Promoting from overflow mOverflowBubbles.remove(bubbleToReturn); + if (mOverflowBubbles.isEmpty()) { + mStateChange.showOverflowChanged = true; + } } else if (mPendingBubbles.containsKey(key)) { // Update while it was pending bubbleToReturn = mPendingBubbles.get(key); @@ -497,19 +504,6 @@ public class BubbleData { } /** - * Explicitly removes a bubble from the overflow, if it exists. - * - * @param bubble the bubble to remove. - */ - public void removeOverflowBubble(Bubble bubble) { - if (bubble == null) return; - if (mOverflowBubbles.remove(bubble)) { - mStateChange.removedOverflowBubble = bubble; - dispatchPendingChanges(); - } - } - - /** * Adds a group key indicating that the summary for this group should be suppressed. * * @param groupKey the group key of the group whose summary should be suppressed. @@ -683,7 +677,6 @@ public class BubbleData { if (indexToRemove == -1) { if (hasOverflowBubbleWithKey(key) && shouldRemoveHiddenBubble) { - Bubble b = getOverflowBubbleWithKey(key); ProtoLog.d(WM_SHELL_BUBBLES, "doRemove - cancel overflow bubble=%s", key); if (b != null) { @@ -693,6 +686,7 @@ public class BubbleData { mOverflowBubbles.remove(b); mStateChange.bubbleRemoved(b, reason); mStateChange.removedOverflowBubble = b; + mStateChange.showOverflowChanged = mOverflowBubbles.isEmpty(); } if (hasSuppressedBubbleWithKey(key) && shouldRemoveHiddenBubble) { Bubble b = getSuppressedBubbleWithKey(key); @@ -792,6 +786,9 @@ public class BubbleData { } ProtoLog.d(WM_SHELL_BUBBLES, "overflowBubble=%s", bubble.getKey()); mLogger.logOverflowAdd(bubble, reason); + if (mOverflowBubbles.isEmpty()) { + mStateChange.showOverflowChanged = true; + } mOverflowBubbles.remove(bubble); mOverflowBubbles.add(0, bubble); mStateChange.addedOverflowBubble = bubble; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 3c788b18429c..be88b3497000 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -536,7 +536,8 @@ public class BubbleStackView extends FrameLayout private OnClickListener mBubbleClickListener = new OnClickListener() { @Override public void onClick(View view) { - mIsDraggingStack = false; // If the touch ended in a click, we're no longer dragging. + // If the touch ended in a click, we're no longer dragging. + onDraggingEnded(); // Bubble clicks either trigger expansion/collapse or a bubble switch, both of which we // shouldn't interrupt. These are quick transitions, so it's not worth trying to adjust @@ -719,8 +720,7 @@ public class BubbleStackView extends FrameLayout mDismissView.hide(); } - mIsDraggingStack = false; - mMagnetizedObject = null; + onDraggingEnded(); // Hide the stack after a delay, if needed. updateTemporarilyInvisibleAnimation(false /* hideImmediately */); @@ -1096,6 +1096,7 @@ public class BubbleStackView extends FrameLayout } else { maybeShowStackEdu(); } + onDraggingEnded(); }); animate() @@ -1153,6 +1154,14 @@ public class BubbleStackView extends FrameLayout } /** + * Reset state related to dragging. + */ + private void onDraggingEnded() { + mIsDraggingStack = false; + mMagnetizedObject = null; + } + + /** * Sets whether or not the stack should become temporarily invisible by moving off the side of * the screen. * @@ -2341,8 +2350,8 @@ public class BubbleStackView extends FrameLayout showScrim(true, null /* runnable */); updateBubbleShadows(mIsExpanded); - updateBadges(false /* setBadgeForCollapsedStack */); mBubbleContainer.setActiveController(mExpandedAnimationController); + updateBadges(false /* setBadgeForCollapsedStack */); updateOverflowVisibility(); updatePointerPosition(false /* forIme */); mExpandedAnimationController.expandFromStack(() -> { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt index 3b3974dc80ec..651bf022e07d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinController.kt @@ -22,7 +22,6 @@ import android.graphics.Rect import android.view.LayoutInflater import android.view.View import android.widget.FrameLayout -import androidx.annotation.VisibleForTesting import androidx.core.view.updateLayoutParams import com.android.wm.shell.R import com.android.wm.shell.bubbles.BubblePositioner @@ -79,7 +78,11 @@ class BubbleExpandedViewPinController( override fun updateLocation(location: BubbleBarLocation) { val view = dropTargetView ?: return - getBounds(location.isOnLeft(view.isLayoutRtl), tempRect) + positioner.getBubbleBarExpandedViewBounds( + location.isOnLeft(view.isLayoutRtl), + false /* isOverflowExpanded */, + tempRect + ) view.updateLayoutParams<FrameLayout.LayoutParams> { width = tempRect.width() height = tempRect.height() @@ -87,17 +90,4 @@ class BubbleExpandedViewPinController( view.x = tempRect.left.toFloat() view.y = tempRect.top.toFloat() } - - private fun getBounds(onLeft: Boolean, out: Rect) { - positioner.getBubbleBarExpandedViewBounds(onLeft, false /* isOverflowExpanded */, out) - val centerX = out.centerX() - val centerY = out.centerY() - out.scale(DROP_TARGET_SCALE) - // Move rect center back to the same position as before scale - out.offset(centerX - out.centerX(), centerY - out.centerY()) - } - - companion object { - @VisibleForTesting const val DROP_TARGET_SCALE = 0.9f - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java index 98dccbbe33e9..da414cc9ae70 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java @@ -389,9 +389,6 @@ public class SystemWindows { public void dispatchDragEvent(DragEvent event) {} @Override - public void updatePointerIcon(float x, float y) {} - - @Override public void dispatchWindowShown() {} @Override @@ -409,5 +406,10 @@ public class SystemWindows { // ignore } } + + @Override + public void dumpWindow(ParcelFileDescriptor pfd) { + + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java index e5f6c370da84..ec3c6013e544 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java @@ -18,6 +18,7 @@ package com.android.wm.shell.common.bubbles; import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.Point; import android.os.Parcel; import android.os.Parcelable; @@ -49,6 +50,10 @@ public class BubbleBarUpdate implements Parcelable { public String unsupressedBubbleKey; @Nullable public BubbleBarLocation bubbleBarLocation; + @Nullable + public Point expandedViewDropTargetSize; + public boolean showOverflowChanged; + public boolean showOverflow; // This is only populated if bubbles have been removed. public List<RemovedBubble> removedBubbles = new ArrayList<>(); @@ -81,12 +86,16 @@ public class BubbleBarUpdate implements Parcelable { suppressedBubbleKey = parcel.readString(); unsupressedBubbleKey = parcel.readString(); removedBubbles = parcel.readParcelableList(new ArrayList<>(), - RemovedBubble.class.getClassLoader()); + RemovedBubble.class.getClassLoader(), RemovedBubble.class); parcel.readStringList(bubbleKeysInOrder); currentBubbleList = parcel.readParcelableList(new ArrayList<>(), - BubbleInfo.class.getClassLoader()); + BubbleInfo.class.getClassLoader(), BubbleInfo.class); bubbleBarLocation = parcel.readParcelable(BubbleBarLocation.class.getClassLoader(), BubbleBarLocation.class); + expandedViewDropTargetSize = parcel.readParcelable(Point.class.getClassLoader(), + Point.class); + showOverflowChanged = parcel.readBoolean(); + showOverflow = parcel.readBoolean(); } /** @@ -102,9 +111,11 @@ public class BubbleBarUpdate implements Parcelable { || suppressedBubbleKey != null || unsupressedBubbleKey != null || !currentBubbleList.isEmpty() - || bubbleBarLocation != null; + || bubbleBarLocation != null + || showOverflowChanged; } + @NonNull @Override public String toString() { return "BubbleBarUpdate{" @@ -121,6 +132,9 @@ public class BubbleBarUpdate implements Parcelable { + " bubbles=" + bubbleKeysInOrder + " currentBubbleList=" + currentBubbleList + " bubbleBarLocation=" + bubbleBarLocation + + " expandedViewDropTargetSize=" + expandedViewDropTargetSize + + " showOverflowChanged=" + showOverflowChanged + + " showOverflow=" + showOverflow + " }"; } @@ -144,6 +158,9 @@ public class BubbleBarUpdate implements Parcelable { parcel.writeStringList(bubbleKeysInOrder); parcel.writeParcelableList(currentBubbleList, flags); parcel.writeParcelable(bubbleBarLocation, flags); + parcel.writeParcelable(expandedViewDropTargetSize, flags); + parcel.writeBoolean(showOverflowChanged); + parcel.writeBoolean(showOverflow); } /** @@ -157,10 +174,11 @@ public class BubbleBarUpdate implements Parcelable { @NonNull public static final Creator<BubbleBarUpdate> CREATOR = - new Creator<BubbleBarUpdate>() { + new Creator<>() { public BubbleBarUpdate createFromParcel(Parcel source) { return new BubbleBarUpdate(source); } + public BubbleBarUpdate[] newArray(int size) { return new BubbleBarUpdate[size]; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/OWNERS new file mode 100644 index 000000000000..08c70314973e --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/OWNERS @@ -0,0 +1,6 @@ +# WM shell sub-module bubble owner +madym@google.com +atsjenk@google.com +liranb@google.com +sukeshram@google.com +mpodolian@google.com diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt index 1e30d8feb132..dba0a985411d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt @@ -16,11 +16,14 @@ package com.android.wm.shell.common.pip import android.app.ActivityTaskManager +import android.app.AppGlobals import android.app.RemoteAction import android.app.WindowConfiguration import android.content.ComponentName import android.content.Context +import android.content.pm.PackageManager import android.os.RemoteException +import android.os.SystemProperties import android.util.DisplayMetrics import android.util.Log import android.util.Pair @@ -135,7 +138,23 @@ object PipUtils { } } + private var isPip2ExperimentEnabled: Boolean? = null + + /** + * Returns true if PiP2 implementation should be used. Besides the trunk stable flag, + * system property can be used to override this read only flag during development. + * It's currently limited to phone form factor, i.e., not enabled on ARC / TV. + */ @JvmStatic - val isPip2ExperimentEnabled: Boolean - get() = Flags.enablePip2Implementation() + fun isPip2ExperimentEnabled(): Boolean { + if (isPip2ExperimentEnabled == null) { + val isArc = AppGlobals.getPackageManager().hasSystemFeature( + "org.chromium.arc", 0) + val isTv = AppGlobals.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_LEANBACK, 0) + isPip2ExperimentEnabled = SystemProperties.getBoolean("wm_shell.pip2", false) || + (Flags.enablePip2Implementation() && !isArc && !isTv) + } + return isPip2ExperimentEnabled as Boolean + } }
\ No newline at end of file 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 5c292f173e5b..bfac24b81d2f 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 @@ -188,6 +188,11 @@ public class CompatUIController implements OnDisplaysChangedListener, */ private boolean mHasShownUserAspectRatioSettingsButton = false; + /** + * This is true when the rechability education is displayed for the first time. + */ + private boolean mIsFirstReachabilityEducationRunning; + public CompatUIController(@NonNull Context context, @NonNull ShellInit shellInit, @NonNull ShellController shellController, @@ -252,9 +257,35 @@ public class CompatUIController implements OnDisplaysChangedListener, removeLayouts(taskInfo.taskId); return; } - + // We're showing the first reachability education so we ignore incoming TaskInfo + // until the education flow has completed or we double tap. + if (mIsFirstReachabilityEducationRunning) { + return; + } + if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) { + if (taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled) { + createOrUpdateLetterboxEduLayout(taskInfo, taskListener); + } else if (!taskInfo.appCompatTaskInfo.isFromLetterboxDoubleTap) { + // In this case the app is letterboxed and the letterbox education + // is disabled. In this case we need to understand if it's the first + // time we show the reachability education. When this is happening + // we need to ignore all the incoming TaskInfo until the education + // completes. If we come from a double tap we follow the normal flow. + final boolean topActivityPillarboxed = + taskInfo.appCompatTaskInfo.isTopActivityPillarboxed(); + final boolean isFirstTimeHorizontalReachabilityEdu = topActivityPillarboxed + && !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(taskInfo); + final boolean isFirstTimeVerticalReachabilityEdu = !topActivityPillarboxed + && !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(taskInfo); + if (isFirstTimeHorizontalReachabilityEdu || isFirstTimeVerticalReachabilityEdu) { + mIsFirstReachabilityEducationRunning = true; + mCompatUIConfiguration.setSeenLetterboxEducation(taskInfo.userId); + createOrUpdateReachabilityEduLayout(taskInfo, taskListener); + return; + } + } + } createOrUpdateCompatLayout(taskInfo, taskListener); - createOrUpdateLetterboxEduLayout(taskInfo, taskListener); createOrUpdateRestartDialogLayout(taskInfo, taskListener); if (mCompatUIConfiguration.getHasSeenLetterboxEducation(taskInfo.userId)) { createOrUpdateReachabilityEduLayout(taskInfo, taskListener); @@ -589,6 +620,7 @@ public class CompatUIController implements OnDisplaysChangedListener, private void onInitialReachabilityEduDismissed(@NonNull TaskInfo taskInfo, @NonNull ShellTaskOrganizer.TaskListener taskListener) { // We need to update the UI otherwise it will not be shown until the user relaunches the app + mIsFirstReachabilityEducationRunning = false; createOrUpdateUserAspectRatioSettingsLayout(taskInfo, taskListener); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java index f195f955692e..3ab1fad2b203 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java @@ -81,6 +81,10 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { super(context, taskInfo, syncQueue, taskListener, displayLayout); mCallback = callback; mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat; + if (Flags.enableDesktopWindowingMode() && Flags.enableWindowingDynamicInitialBounds()) { + // Don't show the SCM button for freeform tasks + mHasSizeCompat &= !taskInfo.isFreeform(); + } mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState; mCompatUIHintsState = compatUIHintsState; @@ -136,6 +140,10 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { final boolean prevHasSizeCompat = mHasSizeCompat; final int prevCameraCompatControlState = mCameraCompatControlState; mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat; + if (Flags.enableDesktopWindowingMode() && Flags.enableWindowingDynamicInitialBounds()) { + // Don't show the SCM button for freeform tasks + mHasSizeCompat &= !taskInfo.isFreeform(); + } mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java index 6e61f22ca563..414a9d1151ac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java @@ -132,6 +132,7 @@ public abstract class Pip2Module { PhonePipMenuController menuPhoneController, PipBoundsAlgorithm pipBoundsAlgorithm, @NonNull PipBoundsState pipBoundsState, + @NonNull PipTransitionState pipTransitionState, @NonNull SizeSpecSource sizeSpecSource, PipMotionHelper pipMotionHelper, FloatingContentCoordinator floatingContentCoordinator, @@ -139,8 +140,9 @@ public abstract class Pip2Module { @ShellMainThread ShellExecutor mainExecutor, Optional<PipPerfHintController> pipPerfHintControllerOptional) { return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm, - pipBoundsState, sizeSpecSource, pipMotionHelper, floatingContentCoordinator, - pipUiEventLogger, mainExecutor, pipPerfHintControllerOptional); + pipBoundsState, pipTransitionState, sizeSpecSource, pipMotionHelper, + floatingContentCoordinator, pipUiEventLogger, mainExecutor, + pipPerfHintControllerOptional); } @WMSingleton @@ -149,9 +151,13 @@ public abstract class Pip2Module { PipBoundsState pipBoundsState, PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm, FloatingContentCoordinator floatingContentCoordinator, - Optional<PipPerfHintController> pipPerfHintControllerOptional) { + PipScheduler pipScheduler, + Optional<PipPerfHintController> pipPerfHintControllerOptional, + PipBoundsAlgorithm pipBoundsAlgorithm, + PipTransitionState pipTransitionState) { return new PipMotionHelper(context, pipBoundsState, menuController, pipSnapAlgorithm, - floatingContentCoordinator, pipPerfHintControllerOptional); + floatingContentCoordinator, pipScheduler, pipPerfHintControllerOptional, + pipBoundsAlgorithm, pipTransitionState); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java index 9bf9fa749373..5af4c3b0a716 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java @@ -41,15 +41,6 @@ public class DesktopModeStatus { public static final boolean IS_DISPLAY_CHANGE_ENABLED = SystemProperties.getBoolean( "persist.wm.debug.desktop_change_display", false); - - /** - * Flag to indicate that desktop stashing is enabled. - * When enabled, swiping home from desktop stashes the open apps. Next app that launches, - * will be added to the desktop. - */ - private static final boolean IS_STASHING_ENABLED = SystemProperties.getBoolean( - "persist.wm.debug.desktop_stashing", false); - /** * Flag to indicate whether to apply shadows to windows in desktop mode. */ @@ -65,7 +56,7 @@ public class DesktopModeStatus { "persist.wm.debug.desktop_use_window_shadows_focused_window", false); /** - * Flag to indicate whether to apply shadows to windows in desktop mode. + * Flag to indicate whether to use rounded corners for windows in desktop mode. */ private static final boolean USE_ROUNDED_CORNERS = SystemProperties.getBoolean( "persist.wm.debug.desktop_use_rounded_corners", true); @@ -109,14 +100,6 @@ public class DesktopModeStatus { } /** - * Return {@code true} if desktop task stashing is enabled when going home. - * Allows users to use home screen to add tasks to desktop. - */ - public static boolean isStashingEnabled() { - return IS_STASHING_ENABLED; - } - - /** * Return whether to use window shadows. * * @param isFocusedWindow whether the window to apply shadows to is focused diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt index 2d508b2e6e3d..6bbc8fec2894 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt @@ -48,7 +48,6 @@ class DesktopModeTaskRepository { val activeTasks: ArraySet<Int> = ArraySet(), val visibleTasks: ArraySet<Int> = ArraySet(), val minimizedTasks: ArraySet<Int> = ArraySet(), - var stashed: Boolean = false ) // Token of the current wallpaper activity, used to remove it when the last task is removed @@ -95,10 +94,8 @@ class DesktopModeTaskRepository { visibleTasksListeners[visibleTasksListener] = executor displayData.keyIterator().forEach { displayId -> val visibleTasksCount = getVisibleTaskCount(displayId) - val stashed = isStashed(displayId) executor.execute { visibleTasksListener.onTasksVisibilityChanged(displayId, visibleTasksCount) - visibleTasksListener.onStashedChanged(displayId, stashed) } } } @@ -400,26 +397,6 @@ class DesktopModeTaskRepository { } /** - * Update stashed status on display with id [displayId] - */ - fun setStashed(displayId: Int, stashed: Boolean) { - val data = displayData.getOrCreate(displayId) - val oldValue = data.stashed - data.stashed = stashed - if (oldValue != stashed) { - KtProtoLog.d( - WM_SHELL_DESKTOP_MODE, - "DesktopTaskRepo: mark stashed=%b displayId=%d", - stashed, - displayId - ) - visibleTasksListeners.forEach { (listener, executor) -> - executor.execute { listener.onStashedChanged(displayId, stashed) } - } - } - } - - /** * Removes and returns the bounds saved before maximizing the given task. */ fun removeBoundsBeforeMaximize(taskId: Int): Rect? { @@ -433,13 +410,6 @@ class DesktopModeTaskRepository { boundsBeforeMaximizeByTaskId.set(taskId, Rect(bounds)) } - /** - * Check if display with id [displayId] has desktop tasks stashed - */ - fun isStashed(displayId: Int): Boolean { - return displayData[displayId]?.stashed ?: false - } - internal fun dump(pw: PrintWriter, prefix: String) { val innerPrefix = "$prefix " pw.println("${prefix}DesktopModeTaskRepository") @@ -455,7 +425,6 @@ class DesktopModeTaskRepository { pw.println("${prefix}Display $displayId:") pw.println("${innerPrefix}activeTasks=${data.activeTasks.toDumpString()}") pw.println("${innerPrefix}visibleTasks=${data.visibleTasks.toDumpString()}") - pw.println("${innerPrefix}stashed=${data.stashed}") } } 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 new file mode 100644 index 000000000000..6da37419737b --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:JvmName("DesktopModeUtils") + +package com.android.wm.shell.desktopmode + +import android.app.ActivityManager.RunningTaskInfo +import android.content.pm.ActivityInfo.isFixedOrientationLandscape +import android.content.pm.ActivityInfo.isFixedOrientationPortrait +import android.content.res.Configuration.ORIENTATION_LANDSCAPE +import android.content.res.Configuration.ORIENTATION_PORTRAIT +import android.graphics.Rect +import android.os.SystemProperties +import android.util.Size +import com.android.wm.shell.common.DisplayLayout + + +val DESKTOP_MODE_INITIAL_BOUNDS_SCALE: Float = SystemProperties + .getInt("persist.wm.debug.desktop_mode_initial_bounds_scale", 75) / 100f + +val DESKTOP_MODE_LANDSCAPE_APP_PADDING: Int = SystemProperties + .getInt("persist.wm.debug.desktop_mode_landscape_app_padding", 25) + + +/** + * Calculates the initial bounds required for an application to fill a scale of the display bounds + * without any letterboxing. This is done by taking into account the applications fullscreen size, + * aspect ratio, orientation and resizability to calculate an area this is compatible with the + * applications previous configuration. + */ +fun calculateInitialBounds( + displayLayout: DisplayLayout, + taskInfo: RunningTaskInfo, + scale: Float = DESKTOP_MODE_INITIAL_BOUNDS_SCALE +): Rect { + val screenBounds = Rect(0, 0, displayLayout.width(), displayLayout.height()) + val appAspectRatio = calculateAspectRatio(taskInfo) + val idealSize = calculateIdealSize(screenBounds, scale) + // If no top activity exists, apps fullscreen bounds and aspect ratio cannot be calculated. + // Instead default to the desired initial bounds. + val topActivityInfo = taskInfo.topActivityInfo + ?: return positionInScreen(idealSize, screenBounds) + + val initialSize: Size = when (taskInfo.configuration.orientation) { + ORIENTATION_LANDSCAPE -> { + if (taskInfo.isResizeable) { + if (isFixedOrientationPortrait(topActivityInfo.screenOrientation)) { + // Respect apps fullscreen width + Size(taskInfo.appCompatTaskInfo.topActivityLetterboxWidth, idealSize.height) + } else { + idealSize + } + } else { + maximumSizeMaintainingAspectRatio(taskInfo, idealSize, + appAspectRatio) + } + } + ORIENTATION_PORTRAIT -> { + val customPortraitWidthForLandscapeApp = screenBounds.width() - + (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2) + if (taskInfo.isResizeable) { + if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) { + // Respect apps fullscreen height and apply custom app width + Size(customPortraitWidthForLandscapeApp, + taskInfo.appCompatTaskInfo.topActivityLetterboxHeight) + } else { + idealSize + } + } else { + if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) { + // Apply custom app width and calculate maximum size + maximumSizeMaintainingAspectRatio( + taskInfo, + Size(customPortraitWidthForLandscapeApp, idealSize.height), + appAspectRatio) + } else { + maximumSizeMaintainingAspectRatio(taskInfo, idealSize, + appAspectRatio) + } + } + } + else -> { + idealSize + } + } + + return positionInScreen(initialSize, screenBounds) +} + +/** + * Calculates the largest size that can fit in a given area while maintaining a specific aspect + * ratio. + */ +private fun maximumSizeMaintainingAspectRatio( + taskInfo: RunningTaskInfo, + targetArea: Size, + aspectRatio: Float +): Size { + val targetHeight = targetArea.height + val targetWidth = targetArea.width + val finalHeight: Int + val finalWidth: Int + if (isFixedOrientationPortrait(taskInfo.topActivityInfo!!.screenOrientation)) { + val tempWidth = (targetHeight / aspectRatio).toInt() + if (tempWidth <= targetWidth) { + finalHeight = targetHeight + finalWidth = tempWidth + } else { + finalWidth = targetWidth + finalHeight = (finalWidth * aspectRatio).toInt() + } + } else { + val tempWidth = (targetHeight * aspectRatio).toInt() + if (tempWidth <= targetWidth) { + finalHeight = targetHeight + finalWidth = tempWidth + } else { + finalWidth = targetWidth + finalHeight = (finalWidth / aspectRatio).toInt() + } + } + return Size(finalWidth, finalHeight) +} + +/** + * Calculates the aspect ratio of an activity from its fullscreen bounds. + */ +private fun calculateAspectRatio(taskInfo: RunningTaskInfo): Float { + if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) { + val appLetterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxWidth + val appLetterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxHeight + return maxOf(appLetterboxWidth, appLetterboxHeight) / + minOf(appLetterboxWidth, appLetterboxHeight).toFloat() + } + val appBounds = taskInfo.configuration.windowConfiguration.appBounds ?: return 1f + return maxOf(appBounds.height(), appBounds.width()) / + minOf(appBounds.height(), appBounds.width()).toFloat() +} + +/** + * Calculates the desired initial bounds for applications in desktop windowing. This is done as a + * scale of the screen bounds. + */ +private fun calculateIdealSize(screenBounds: Rect, scale: Float): Size { + val width = (screenBounds.width() * scale).toInt() + val height = (screenBounds.height() * scale).toInt() + return Size(width, height) +} + +/** + * Adjusts bounds to be positioned in the middle of the screen. + */ +private fun positionInScreen(desiredSize: Size, screenBounds: Rect): Rect { + // TODO(b/325240051): Position apps with bottom heavy offset + val heightOffset = (screenBounds.height() - desiredSize.height) / 2 + val widthOffset = (screenBounds.width() - desiredSize.width) / 2 + return Rect(widthOffset, heightOffset, + desiredSize.width + widthOffset, desiredSize.height + heightOffset) +} 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 f7bfb86a5158..b2bdbfefb9aa 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 @@ -47,6 +47,7 @@ import android.window.TransitionInfo import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction import androidx.annotation.BinderThread +import com.android.internal.annotations.VisibleForTesting import com.android.internal.policy.ScreenDecorationsUtils import com.android.window.flags.Flags import com.android.wm.shell.RootTaskDisplayAreaOrganizer @@ -85,7 +86,6 @@ import com.android.wm.shell.util.KtProtoLog import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility import com.android.wm.shell.windowdecor.MoveToDesktopAnimator import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener -import com.android.wm.shell.windowdecor.extension.isFreeform import com.android.wm.shell.windowdecor.extension.isFullscreen import java.io.PrintWriter import java.util.Optional @@ -203,6 +203,11 @@ class DesktopTasksController( dragAndDropController.addListener(this) } + @VisibleForTesting + fun getVisualIndicator(): DesktopModeVisualIndicator? { + return visualIndicator + } + fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) { toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener) enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener) @@ -235,34 +240,6 @@ class DesktopTasksController( } } - /** - * Stash desktop tasks on display with id [displayId]. - * - * When desktop tasks are stashed, launcher home screen icons are fully visible. New apps - * launched in this state will be added to the desktop. Existing desktop tasks will be brought - * back to front during the launch. - */ - fun stashDesktopApps(displayId: Int) { - if (DesktopModeStatus.isStashingEnabled()) { - KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: stashDesktopApps") - desktopModeTaskRepository.setStashed(displayId, true) - } - } - - /** - * Clear the stashed state for the given display - */ - fun hideStashedDesktopApps(displayId: Int) { - if (DesktopModeStatus.isStashingEnabled()) { - KtProtoLog.v( - WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: hideStashedApps displayId=%d", - displayId - ) - desktopModeTaskRepository.setStashed(displayId, false) - } - } - /** Get number of tasks that are marked as visible */ fun getVisibleTaskCount(displayId: Int): Int { return desktopModeTaskRepository.getVisibleTaskCount(displayId) @@ -605,8 +582,9 @@ class DesktopTasksController( } /** - * Quick-resizes a desktop task, toggling between the stable bounds and the last saved bounds - * if available or the default bounds otherwise. + * 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). */ fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo) { val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return @@ -623,7 +601,11 @@ class DesktopTasksController( if (taskBoundsBeforeMaximize != null) { destinationBounds.set(taskBoundsBeforeMaximize) } else { - destinationBounds.set(getDefaultDesktopTaskBounds(displayLayout)) + if (Flags.enableWindowingDynamicInitialBounds()){ + destinationBounds.set(calculateInitialBounds(displayLayout, taskInfo)) + } else { + destinationBounds.set(getDefaultDesktopTaskBounds(displayLayout)) + } } } else { // Save current bounds so that task can be restored back to original bounds if necessary @@ -861,8 +843,6 @@ class DesktopTasksController( val result = triggerTask?.let { task -> when { request.type == TRANSIT_TO_BACK -> handleBackNavigation(task) - // If display has tasks stashed, handle as stashed launch - task.isStashed -> handleStashedTaskLaunch(task, transition) // Check if the task has a top transparent activity shouldLaunchAsModal(task) -> handleTransparentTaskLaunch(task) // Check if fullscreen task should be updated @@ -901,12 +881,8 @@ class DesktopTasksController( .forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) } } - private val TaskInfo.isStashed: Boolean - get() = desktopModeTaskRepository.isStashed(displayId) - - private fun shouldLaunchAsModal(task: TaskInfo): Boolean { - return Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task) - } + private fun shouldLaunchAsModal(task: TaskInfo) = + Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task) private fun shouldRemoveWallpaper(request: TransitionRequestInfo): Boolean { return Flags.enableDesktopWindowingWallpaperActivity() && @@ -966,24 +942,6 @@ class DesktopTasksController( return null } - private fun handleStashedTaskLaunch( - task: RunningTaskInfo, - transition: IBinder - ): WindowContainerTransaction { - KtProtoLog.d( - WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: launch apps with stashed on transition taskId=%d", - task.taskId - ) - val wct = WindowContainerTransaction() - val taskToMinimize = - bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId) - addMoveToDesktopChanges(wct, task) - desktopModeTaskRepository.setStashed(task.displayId, false) - addPendingMinimizeTransition(transition, taskToMinimize) - return wct - } - // Always launch transparent tasks in fullscreen. private fun handleTransparentTaskLaunch(task: RunningTaskInfo): WindowContainerTransaction? { // Already fullscreen, no-op. @@ -1011,6 +969,7 @@ class DesktopTasksController( wct: WindowContainerTransaction, taskInfo: RunningTaskInfo ) { + val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!! val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode val targetWindowingMode = if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) { @@ -1019,6 +978,9 @@ class DesktopTasksController( } else { WINDOWING_MODE_FREEFORM } + if (Flags.enableWindowingDynamicInitialBounds()) { + wct.setBounds(taskInfo.token, calculateInitialBounds(displayLayout, taskInfo)) + } wct.setWindowingMode(taskInfo.token, targetWindowingMode) wct.reorder(taskInfo.token, true /* onTop */) if (isDesktopDensityOverrideSet()) { @@ -1239,13 +1201,17 @@ class DesktopTasksController( * @param y height of drag, to be checked against status bar height. */ fun onDragPositioningEndThroughStatusBar(inputCoordinates: PointF, taskInfo: RunningTaskInfo) { - val indicator = visualIndicator ?: return + val indicator = getVisualIndicator() ?: return val indicatorType = indicator .updateIndicatorType(inputCoordinates, taskInfo.windowingMode) when (indicatorType) { DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> { val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return - finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout)) + if (Flags.enableWindowingDynamicInitialBounds()) { + finalizeDragToDesktop(taskInfo, calculateInitialBounds(displayLayout, taskInfo)) + } else { + finalizeDragToDesktop(taskInfo, getDefaultDesktopTaskBounds(displayLayout)) + } } DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR, DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> { @@ -1449,25 +1415,25 @@ class DesktopTasksController( ) { c -> c.showDesktopApps(displayId, remoteTransition) } } - override fun stashDesktopApps(displayId: Int) { + override fun showDesktopApp(taskId: Int) { ExecutorUtils.executeRemoteCallWithTaskPermission( controller, - "stashDesktopApps" - ) { c -> c.stashDesktopApps(displayId) } + "showDesktopApp" + ) { c -> c.moveTaskToFront(taskId) } } - override fun hideStashedDesktopApps(displayId: Int) { - ExecutorUtils.executeRemoteCallWithTaskPermission( - controller, - "hideStashedDesktopApps" - ) { c -> c.hideStashedDesktopApps(displayId) } + override fun stashDesktopApps(displayId: Int) { + KtProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "IDesktopModeImpl: stashDesktopApps is deprecated" + ) } - override fun showDesktopApp(taskId: Int) { - ExecutorUtils.executeRemoteCallWithTaskPermission( - controller, - "showDesktopApp" - ) { c -> c.moveTaskToFront(taskId) } + override fun hideStashedDesktopApps(displayId: Int) { + KtProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "IDesktopModeImpl: hideStashedDesktopApps is deprecated" + ) } override fun getVisibleTaskCount(displayId: Int): Int { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl index fa4352241193..c36f8deb6ecc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl @@ -28,10 +28,10 @@ interface IDesktopMode { /** Show apps on the desktop on the given display */ void showDesktopApps(int displayId, in RemoteTransition remoteTransition); - /** Stash apps on the desktop to allow launching another app from home screen */ + /** @deprecated use {@link #showDesktopApps} instead. */ void stashDesktopApps(int displayId); - /** Hide apps that may be stashed */ + /** @deprecated this is no longer supported. */ void hideStashedDesktopApps(int displayId); /** Bring task with the given id to front */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java index ecb53dc17a48..59d696918448 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java @@ -16,11 +16,11 @@ package com.android.wm.shell.draganddrop; -import static android.app.StatusBarManager.DISABLE2_NONE; import static android.app.StatusBarManager.DISABLE_NONE; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS; import static android.content.pm.ActivityInfo.CONFIG_UI_MODE; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME; import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; @@ -516,20 +516,18 @@ public class DragLayout extends LinearLayout } private void animateFullscreenContainer(boolean visible) { - int flags = visible ? HIDE_STATUS_BAR_FLAGS : DISABLE_NONE; - StatusBarManager.DisableInfo disableInfo = new StatusBarManager.DisableInfo(flags, - DISABLE2_NONE); - mStatusBarManager.requestDisabledComponent(disableInfo, "animateFullscreenContainer"); + mStatusBarManager.disable(visible + ? HIDE_STATUS_BAR_FLAGS + : DISABLE_NONE); // We're only using the first drop zone if there is one fullscreen target mDropZoneView1.setShowingMargin(visible); mDropZoneView1.setShowingHighlight(visible); } private void animateSplitContainers(boolean visible, Runnable animCompleteCallback) { - int flags = visible ? HIDE_STATUS_BAR_FLAGS : DISABLE_NONE; - StatusBarManager.DisableInfo disableInfo = new StatusBarManager.DisableInfo(flags, - DISABLE2_NONE); - mStatusBarManager.requestDisabledComponent(disableInfo, "animateSplitContainers"); + mStatusBarManager.disable(visible + ? HIDE_STATUS_BAR_FLAGS + : DISABLE_NONE); mDropZoneView1.setShowingMargin(visible); mDropZoneView2.setShowingMargin(visible); Animator animator = mDropZoneView1.getAnimator(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java index 7b1ef5c6cddd..a749019046f8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java @@ -39,7 +39,7 @@ public interface Pip { * @param isSysUiStateValid Is SysUI state valid or not. * @param flag Current SysUI state. */ - default void onSystemUiStateChanged(boolean isSysUiStateValid, int flag) { + default void onSystemUiStateChanged(boolean isSysUiStateValid, long flag) { } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 139cde2c66f7..85f9194ac804 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -847,7 +847,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb } } - private void onSystemUiStateChanged(boolean isValidState, int flag) { + private void onSystemUiStateChanged(boolean isValidState, long flag) { mTouchHandler.onSystemUiStateChanged(isValidState); } @@ -1195,7 +1195,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb } @Override - public void onSystemUiStateChanged(boolean isSysUiStateValid, int flag) { + public void onSystemUiStateChanged(boolean isSysUiStateValid, long flag) { mMainExecutor.execute(() -> { PipController.this.onSystemUiStateChanged(isSysUiStateValid, flag); }); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java index 03547a55fa27..b757b00f16dd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java @@ -53,7 +53,7 @@ public class PipInputConsumer { * Listener interface for callers to learn when this class is registered or unregistered with * window manager */ - private interface RegistrationListener { + interface RegistrationListener { void onRegistrationChanged(boolean isRegistered); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java index 619bed4e19ca..a097a0ffa47d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java @@ -31,7 +31,9 @@ import android.annotation.Nullable; import android.content.Context; import android.graphics.PointF; import android.graphics.Rect; +import android.os.Bundle; import android.os.Debug; +import android.view.SurfaceControl; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; @@ -39,6 +41,7 @@ import com.android.wm.shell.animation.FloatProperties; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; import com.android.wm.shell.common.pip.PipAppOpsListener; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipPerfHintController; import com.android.wm.shell.common.pip.PipSnapAlgorithm; @@ -57,6 +60,7 @@ import java.util.function.Consumer; public class PipMotionHelper implements PipAppOpsListener.Callback, FloatingContentCoordinator.FloatingContent { private static final String TAG = "PipMotionHelper"; + private static final String FLING_BOUNDS_CHANGE = "fling_bounds_change"; private static final boolean DEBUG = false; private static final int SHRINK_STACK_FROM_MENU_DURATION = 250; @@ -72,7 +76,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private final Context mContext; private @NonNull PipBoundsState mPipBoundsState; - + private @NonNull PipBoundsAlgorithm mPipBoundsAlgorithm; + private @NonNull PipScheduler mPipScheduler; + private @NonNull PipTransitionState mPipTransitionState; private PhonePipMenuController mMenuController; private PipSnapAlgorithm mSnapAlgorithm; @@ -145,6 +151,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private boolean mDismissalPending = false; /** + * Set to true if bounds change transition has been scheduled from PipMotionHelper. + */ + private boolean mWaitingForBoundsChangeTransition = false; + + /** * Gets set in {@link #animateToExpandedState(Rect, Rect, Rect, Runnable)}, this callback is * used to show menu activity when the expand animation is completed. */ @@ -152,22 +163,25 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState, PhonePipMenuController menuController, PipSnapAlgorithm snapAlgorithm, - FloatingContentCoordinator floatingContentCoordinator, - Optional<PipPerfHintController> pipPerfHintControllerOptional) { + FloatingContentCoordinator floatingContentCoordinator, PipScheduler pipScheduler, + Optional<PipPerfHintController> pipPerfHintControllerOptional, + PipBoundsAlgorithm pipBoundsAlgorithm, PipTransitionState pipTransitionState) { mContext = context; mPipBoundsState = pipBoundsState; + mPipBoundsAlgorithm = pipBoundsAlgorithm; + mPipScheduler = pipScheduler; mMenuController = menuController; mSnapAlgorithm = snapAlgorithm; mFloatingContentCoordinator = floatingContentCoordinator; mPipPerfHintController = pipPerfHintControllerOptional.orElse(null); mResizePipUpdateListener = (target, values) -> { if (mPipBoundsState.getMotionBoundsState().isInMotion()) { - /* - mPipTaskOrganizer.scheduleUserResizePip(getBounds(), - mPipBoundsState.getMotionBoundsState().getBoundsInMotion(), null); - */ + mPipScheduler.scheduleUserResizePip( + mPipBoundsState.getMotionBoundsState().getBoundsInMotion()); } }; + mPipTransitionState = pipTransitionState; + mPipTransitionState.addPipTransitionStateChangedListener(this::onPipTransitionStateChanged); } void init() { @@ -236,12 +250,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, mPipBoundsState.setBounds(toBounds); } else { mPipBoundsState.getMotionBoundsState().setBoundsInMotion(toBounds); - /* - mPipTaskOrganizer.scheduleUserResizePip(getBounds(), toBounds, - (Rect newBounds) -> { - mMenuController.updateMenuLayout(newBounds); - }); - */ + mPipScheduler.scheduleUserResizePip(toBounds); } } else { // If PIP is 'catching up' after being stuck in the dismiss target, update the animation @@ -552,11 +561,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, /** Set new fling configs whose min/max values respect the given movement bounds. */ private void rebuildFlingConfigs() { mFlingConfigX = new PhysicsAnimator.FlingConfig(DEFAULT_FRICTION, - mPipBoundsState.getMovementBounds().left, - mPipBoundsState.getMovementBounds().right); + mPipBoundsAlgorithm.getMovementBounds(getBounds()).left, + mPipBoundsAlgorithm.getMovementBounds(getBounds()).right); mFlingConfigY = new PhysicsAnimator.FlingConfig(DEFAULT_FRICTION, - mPipBoundsState.getMovementBounds().top, - mPipBoundsState.getMovementBounds().bottom); + mPipBoundsAlgorithm.getMovementBounds(getBounds()).top, + mPipBoundsAlgorithm.getMovementBounds(getBounds()).bottom); final Rect insetBounds = mPipBoundsState.getDisplayLayout().stableInsets(); mStashConfigX = new PhysicsAnimator.FlingConfig( DEFAULT_FRICTION, @@ -623,22 +632,15 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private void onBoundsPhysicsAnimationEnd() { // The physics animation ended, though we may not necessarily be done animating, such as // when we're still dragging after moving out of the magnetic target. - if (!mDismissalPending - && !mSpringingToTouch - && !mMagnetizedPip.getObjectStuckToTarget()) { - // All motion operations have actually finished. - mPipBoundsState.setBounds( - mPipBoundsState.getMotionBoundsState().getBoundsInMotion()); - mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded(); - if (!mDismissalPending) { - // do not schedule resize if PiP is dismissing, which may cause app re-open to - // mBounds instead of its normal bounds. - // mPipTaskOrganizer.scheduleFinishResizePip(getBounds()); - } + if (!mDismissalPending && !mSpringingToTouch && !mMagnetizedPip.getObjectStuckToTarget()) { + // do not schedule resize if PiP is dismissing, which may cause app re-open to + // mBounds instead of its normal bounds. + Bundle extra = new Bundle(); + extra.putBoolean(FLING_BOUNDS_CHANGE, true); + mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra); + return; } - mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded(); - mSpringingToTouch = false; - mDismissalPending = false; + settlePipBoundsAfterPhysicsAnimation(true /* animatingAfter */); cleanUpHighPerfSessionMaybe(); } @@ -662,7 +664,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, + " callers=\n%s", TAG, toBounds, Debug.getCallers(5, " ")); } if (!toBounds.equals(getBounds())) { - // mPipTaskOrganizer.scheduleResizePip(toBounds, mUpdateBoundsCallback); + mPipScheduler.scheduleAnimateResizePip(toBounds); } } @@ -685,6 +687,60 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, // setAnimatingToBounds(toBounds); } + private void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState, + @PipTransitionState.TransitionState int newState, + @Nullable Bundle extra) { + switch (newState) { + case PipTransitionState.SCHEDULED_BOUNDS_CHANGE: + if (!extra.getBoolean(FLING_BOUNDS_CHANGE)) break; + + // If touch is turned off and we are in a fling animation, schedule a transition. + mWaitingForBoundsChangeTransition = true; + mPipScheduler.scheduleAnimateResizePip( + mPipBoundsState.getMotionBoundsState().getBoundsInMotion()); + break; + case PipTransitionState.CHANGING_PIP_BOUNDS: + if (!mWaitingForBoundsChangeTransition) break; + + // If bounds change transition was scheduled from this class, handle leash updates. + mWaitingForBoundsChangeTransition = false; + SurfaceControl.Transaction startTx = extra.getParcelable( + PipTransition.PIP_START_TX, SurfaceControl.Transaction.class); + Rect destinationBounds = extra.getParcelable( + PipTransition.PIP_DESTINATION_BOUNDS, Rect.class); + startTx.setPosition(mPipTransitionState.mPinnedTaskLeash, + destinationBounds.left, destinationBounds.top); + startTx.apply(); + + // All motion operations have actually finished, so make bounds cache updates. + settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */); + cleanUpHighPerfSessionMaybe(); + + // Setting state to CHANGED_PIP_BOUNDS applies finishTx and notifies Core. + mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS); + break; + case PipTransitionState.EXITING_PIP: + // We need to force finish any local animators if about to leave PiP, to avoid + // breaking the state (e.g. leashes are cleaned up upon exit). + if (!mPipBoundsState.getMotionBoundsState().isInMotion()) break; + cancelPhysicsAnimation(); + settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */); + } + } + + private void settlePipBoundsAfterPhysicsAnimation(boolean animatingAfter) { + if (!animatingAfter) { + // The physics animation ended, though we may not necessarily be done animating, such as + // when we're still dragging after moving out of the magnetic target. Only set the final + // bounds state and clear motion bounds completely if the whole animation is over. + mPipBoundsState.setBounds(mPipBoundsState.getMotionBoundsState().getBoundsInMotion()); + mPipBoundsState.getMotionBoundsState().onAllAnimationsEnded(); + } + mPipBoundsState.getMotionBoundsState().onPhysicsAnimationEnded(); + mSpringingToTouch = false; + mDismissalPending = false; + } + /** * Returns a MagnetizedObject wrapper for PIP's animated bounds. This is provided to the * magnetic dismiss target so it can calculate PIP's size and position. 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 72fa3badeb93..c5b0de31f104 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 @@ -25,16 +25,19 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Rect; +import android.view.SurfaceControl; import android.window.WindowContainerTransaction; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; +import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -156,4 +159,20 @@ public class PipScheduler { wct.setBounds(mPipTransitionState.mPipTaskToken, toBounds); mPipTransitionController.startResizeTransition(wct); } + + /** + * Directly perform a scaled matrix transformation on the leash. This will not perform any + * {@link WindowContainerTransaction}. + */ + public void scheduleUserResizePip(Rect toBounds) { + if (toBounds.isEmpty()) { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Attempted to user resize PIP to empty bounds, aborting.", TAG); + return; + } + SurfaceControl leash = mPipTransitionState.mPinnedTaskLeash; + final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); + tx.setPosition(leash, toBounds.left, toBounds.top); + tx.apply(); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java index 472003cb435f..9c6e3ea494fa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java @@ -16,6 +16,8 @@ package com.android.wm.shell.pip2.phone; +import static android.view.WindowManager.INPUT_CONSUMER_PIP; + import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASHING; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.PIP_STASH_MINIMUM_VELOCITY_THRESHOLD; import static com.android.wm.shell.common.pip.PipBoundsState.STASH_TYPE_LEFT; @@ -30,18 +32,19 @@ import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_PICTURE_ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; -import android.content.ComponentName; import android.content.Context; import android.content.res.Resources; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; +import android.os.Bundle; import android.provider.DeviceConfig; import android.util.Size; import android.view.DisplayCutout; import android.view.InputEvent; import android.view.MotionEvent; import android.view.ViewConfiguration; +import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -80,6 +83,7 @@ public class PipTouchHandler { private final Context mContext; private final PipBoundsAlgorithm mPipBoundsAlgorithm; @NonNull private final PipBoundsState mPipBoundsState; + @NonNull private final PipTransitionState mPipTransitionState; @NonNull private final SizeSpecSource mSizeSpecSource; private final PipUiEventLogger mPipUiEventLogger; private final PipDismissTargetHandler mPipDismissTargetHandler; @@ -125,6 +129,7 @@ public class PipTouchHandler { private final FloatingContentCoordinator mFloatingContentCoordinator; private PipMotionHelper mMotionHelper; private PipTouchGesture mGesture; + private PipInputConsumer mPipInputConsumer; // Temp vars private final Rect mTmpBounds = new Rect(); @@ -167,6 +172,7 @@ public class PipTouchHandler { PhonePipMenuController menuController, PipBoundsAlgorithm pipBoundsAlgorithm, @NonNull PipBoundsState pipBoundsState, + @NonNull PipTransitionState pipTransitionState, @NonNull SizeSpecSource sizeSpecSource, PipMotionHelper pipMotionHelper, FloatingContentCoordinator floatingContentCoordinator, @@ -179,6 +185,9 @@ public class PipTouchHandler { mAccessibilityManager = context.getSystemService(AccessibilityManager.class); mPipBoundsAlgorithm = pipBoundsAlgorithm; mPipBoundsState = pipBoundsState; + + mPipTransitionState = pipTransitionState; + mPipTransitionState.addPipTransitionStateChangedListener(this::onPipTransitionStateChanged); mSizeSpecSource = sizeSpecSource; mMenuController = menuController; mPipUiEventLogger = pipUiEventLogger; @@ -227,6 +236,11 @@ public class PipTouchHandler { mPipResizeGestureHandler.init(); mPipDismissTargetHandler.init(); + mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(), + INPUT_CONSUMER_PIP, mMainExecutor); + mPipInputConsumer.setInputListener(this::handleTouchEvent); + mPipInputConsumer.setRegistrationListener(this::onRegistrationChanged); + mEnableStash = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_SYSTEMUI, PIP_STASHING, @@ -294,19 +308,17 @@ public class PipTouchHandler { void onActivityPinned() { mPipDismissTargetHandler.createOrUpdateDismissTarget(); - mPipResizeGestureHandler.onActivityPinned(); mFloatingContentCoordinator.onContentAdded(mMotionHelper); + mPipInputConsumer.registerInputConsumer(); } - void onActivityUnpinned(ComponentName topPipActivity) { - if (topPipActivity == null) { - // Clean up state after the last PiP activity is removed - mPipDismissTargetHandler.cleanUpDismissTarget(); - - mFloatingContentCoordinator.onContentRemoved(mMotionHelper); - } + void onActivityUnpinned() { + // Clean up state after the last PiP activity is removed + mPipDismissTargetHandler.cleanUpDismissTarget(); + mFloatingContentCoordinator.onContentRemoved(mMotionHelper); mPipResizeGestureHandler.onActivityUnpinned(); + mPipInputConsumer.unregisterInputConsumer(); } void onPinnedStackAnimationEnded( @@ -512,6 +524,7 @@ public class PipTouchHandler { return true; } + /* if ((ev.getAction() == MotionEvent.ACTION_DOWN || mTouchState.isUserInteracting()) && mPipDismissTargetHandler.maybeConsumeMotionEvent(ev)) { // If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event @@ -528,11 +541,13 @@ public class PipTouchHandler { return true; } - if (!mTouchState.isUserInteracting()) { + // Ignore the motion event When the entry animation is waiting to be started + if (!mTouchState.isUserInteracting() && mPipTaskOrganizer.isEntryScheduled()) { ProtoLog.wtf(WM_SHELL_PICTURE_IN_PICTURE, "%s: Waiting to start the entry animation, skip the motion event.", TAG); return true; } + */ // Update the touch state mTouchState.onTouchEvent(ev); @@ -808,7 +823,7 @@ public class PipTouchHandler { mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mPipBoundsState.getMovementBounds().bottom; mMotionHelper.setSpringingToTouch(false); - // mPipDismissTargetHandler.setTaskLeash(mPipTaskOrganizer.getSurfaceControl()); + mPipDismissTargetHandler.setTaskLeash(mPipTransitionState.mPinnedTaskLeash); // If the menu is still visible then just poke the menu // so that it will timeout after the user stops touching it @@ -880,7 +895,8 @@ public class PipTouchHandler { // Reset the touch state on up before the fling settles mTouchState.reset(); if (mEnableStash && shouldStash(vel, getPossiblyMotionBounds())) { - mMotionHelper.stashToEdge(vel.x, vel.y, this::stashEndAction /* endAction */); + // mMotionHelper.stashToEdge(vel.x, vel.y, + // this::stashEndAction /* endAction */); } else { if (mPipBoundsState.isStashed()) { // Reset stashed state if previously stashed @@ -1059,6 +1075,27 @@ public class PipTouchHandler { mPipResizeGestureHandler.setOhmOffset(offset); } + private void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState, + @PipTransitionState.TransitionState int newState, + @Nullable Bundle extra) { + switch (newState) { + case PipTransitionState.ENTERED_PIP: + onActivityPinned(); + mTouchState.setAllowInputEvents(true); + break; + case PipTransitionState.EXITED_PIP: + mTouchState.setAllowInputEvents(false); + onActivityUnpinned(); + break; + case PipTransitionState.SCHEDULED_BOUNDS_CHANGE: + mTouchState.setAllowInputEvents(false); + break; + case PipTransitionState.CHANGED_PIP_BOUNDS: + mTouchState.setAllowInputEvents(true); + break; + } + } + /** * Dumps the {@link PipTouchHandler} state. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index 8b2d0ddf2510..7dddd2748f83 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -61,11 +61,15 @@ import com.android.wm.shell.transition.Transitions; public class PipTransition extends PipTransitionController implements PipTransitionState.PipTransitionStateChangedListener { private static final String TAG = PipTransition.class.getSimpleName(); + + // Used when for ENTERING_PIP state update. private static final String PIP_TASK_TOKEN = "pip_task_token"; private static final String PIP_TASK_LEASH = "pip_task_leash"; - private static final String PIP_START_TX = "pip_start_tx"; - private static final String PIP_FINISH_TX = "pip_finish_tx"; - private static final String PIP_DESTINATION_BOUNDS = "pip_dest_bounds"; + + // Used for PiP CHANGING_BOUNDS state update. + static final String PIP_START_TX = "pip_start_tx"; + static final String PIP_FINISH_TX = "pip_finish_tx"; + static final String PIP_DESTINATION_BOUNDS = "pip_dest_bounds"; /** * The fixed start delay in ms when fading out the content overlay from bounds animation. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java index 9a9c59e2fa8e..8204d41a9833 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java @@ -71,17 +71,21 @@ public class PipTransitionState { // State for app finishing drawing in PiP mode as a final step in enter PiP flow. public static final int ENTERED_PIP = 3; - // State for scheduling a transition to change PiP bounds. - public static final int CHANGING_PIP_BOUNDS = 4; + // State to indicate we have scheduled a PiP bounds change transition. + public static final int SCHEDULED_BOUNDS_CHANGE = 4; - // State for app potentially finishing drawing in new PiP bounds after resize is complete. - public static final int CHANGED_PIP_BOUNDS = 5; + // State for the start of playing a transition to change PiP bounds. At this point, WM Core + // is aware of the new PiP bounds, but Shell might still be continuing animating. + public static final int CHANGING_PIP_BOUNDS = 5; + + // State for finishing animating into new PiP bounds after resize is complete. + public static final int CHANGED_PIP_BOUNDS = 6; // State for starting exiting PiP. - public static final int EXITING_PIP = 6; + public static final int EXITING_PIP = 7; // State for finishing exit PiP flow. - public static final int EXITED_PIP = 7; + public static final int EXITED_PIP = 8; private static final int FIRST_CUSTOM_STATE = 1000; @@ -92,6 +96,7 @@ public class PipTransitionState { SWIPING_TO_PIP, ENTERING_PIP, ENTERED_PIP, + SCHEDULED_BOUNDS_CHANGE, CHANGING_PIP_BOUNDS, CHANGED_PIP_BOUNDS, EXITING_PIP, @@ -165,10 +170,11 @@ public class PipTransitionState { * @param extra a bundle passed to the subscribed listeners to resolve/cache extra info. */ public void setState(@TransitionState int state, @Nullable Bundle extra) { - if (state == ENTERING_PIP || state == SWIPING_TO_PIP) { - // Whenever we are entering PiP caller must provide extra state to set as well. + if (state == ENTERING_PIP || state == SWIPING_TO_PIP + || state == SCHEDULED_BOUNDS_CHANGE || state == CHANGING_PIP_BOUNDS) { + // States listed above require extra bundles to be provided. Preconditions.checkArgument(extra != null && !extra.isEmpty(), - "No extra bundle for either ENTERING_PIP or SWIPING_TO_PIP state."); + "No extra bundle for " + stateToString(state) + " state."); } if (mState != state) { dispatchPipTransitionStateChanged(mState, state, extra); @@ -254,23 +260,24 @@ public class PipTransitionState { return ++mPrevCustomState; } - private String stateToString() { - switch (mState) { + private static String stateToString(int state) { + switch (state) { case UNDEFINED: return "undefined"; case SWIPING_TO_PIP: return "swiping_to_pip"; case ENTERING_PIP: return "entering-pip"; case ENTERED_PIP: return "entered-pip"; + case SCHEDULED_BOUNDS_CHANGE: return "scheduled_bounds_change"; case CHANGING_PIP_BOUNDS: return "changing-bounds"; case CHANGED_PIP_BOUNDS: return "changed-bounds"; case EXITING_PIP: return "exiting-pip"; case EXITED_PIP: return "exited-pip"; } - throw new IllegalStateException("Unknown state: " + mState); + throw new IllegalStateException("Unknown state: " + state); } @Override public String toString() { return String.format("PipTransitionState(mState=%s, mInSwipePipToHomeTransition=%b)", - stateToString(), mInSwipePipToHomeTransition); + stateToString(mState), mInSwipePipToHomeTransition); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java index ad29d15019c5..19af3d544b36 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java @@ -52,7 +52,7 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, - Consts.TAG_WM_SHELL), + Consts.TAG_WM_DESKTOP_MODE), WM_SHELL_FLOATING_APPS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), WM_SHELL_FOLDABLE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, @@ -120,6 +120,7 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { private static final String TAG_WM_SHELL = "WindowManagerShell"; private static final String TAG_WM_STARTING_WINDOW = "ShellStartingWindow"; private static final String TAG_WM_SPLIT_SCREEN = "ShellSplitScreen"; + private static final String TAG_WM_DESKTOP_MODE = "ShellDesktopMode"; private static final boolean ENABLE_DEBUG = true; private static final boolean ENABLE_LOG_TO_PROTO_DEBUG = true; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index 4d02ec26e18e..968b27b62490 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -426,7 +426,8 @@ public class DefaultMixedHandler implements MixedTransitionHandler, ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Converting mixed transition into a keyguard transition"); // Consume the original mixed transition - onTransitionConsumed(transition, false, null); + mActiveTransitions.remove(mixed); + mixed.onTransitionConsumed(transition, false, null); return true; } else { // Keyguard handler cannot handle it, process through original mixed 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 f3ef7c17fac3..dfdb58a50892 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 @@ -278,9 +278,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) { if (visible && stage != STAGE_TYPE_UNDEFINED) { DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId); - if (decor != null && DesktopModeStatus.canEnterDesktopMode(mContext)) { - mDesktopTasksController.moveToSplit(decor.mTaskInfo); + if (decor == null || !DesktopModeStatus.canEnterDesktopMode(mContext) + || decor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) { + return; } + mDesktopTasksController.moveToSplit(decor.mTaskInfo); } } }); 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 f7516dabe1ac..4c347adcb486 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 @@ -110,6 +110,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private ResizeVeil mResizeVeil; private Bitmap mAppIconBitmap; + private Bitmap mResizeVeilBitmap; + private CharSequence mAppName; private ExclusionRegionListener mExclusionRegionListener; @@ -468,11 +470,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin PackageManager pm = mContext.getApplicationContext().getPackageManager(); final IconProvider provider = new IconProvider(mContext); final Drawable appIconDrawable = provider.getIcon(activityInfo); - final Resources resources = mContext.getResources(); - final BaseIconFactory factory = new BaseIconFactory(mContext, - resources.getDisplayMetrics().densityDpi, - resources.getDimensionPixelSize(R.dimen.desktop_mode_caption_icon_radius)); - mAppIconBitmap = factory.createScaledBitmap(appIconDrawable, MODE_DEFAULT); + final BaseIconFactory headerIconFactory = createIconFactory(mContext, + R.dimen.desktop_mode_caption_icon_radius); + mAppIconBitmap = headerIconFactory.createScaledBitmap(appIconDrawable, MODE_DEFAULT); + + final BaseIconFactory resizeVeilIconFactory = createIconFactory(mContext, + R.dimen.desktop_mode_resize_veil_icon_size); + mResizeVeilBitmap = resizeVeilIconFactory + .createScaledBitmap(appIconDrawable, MODE_DEFAULT); + final ApplicationInfo applicationInfo = activityInfo.applicationInfo; mAppName = pm.getApplicationLabel(applicationInfo); } finally { @@ -480,6 +486,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } } + private BaseIconFactory createIconFactory(Context context, int dimensions) { + final Resources resources = context.getResources(); + final int densityDpi = resources.getDisplayMetrics().densityDpi; + final int iconSize = resources.getDimensionPixelSize(dimensions); + return new BaseIconFactory(context, densityDpi, iconSize); + } + private void closeDragResizeListener() { if (mDragResizeListener == null) { return; @@ -495,7 +508,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private void createResizeVeilIfNeeded() { if (mResizeVeil != null) return; loadAppInfoIfNeeded(); - mResizeVeil = new ResizeVeil(mContext, mDisplayController, mAppIconBitmap, mTaskInfo, + mResizeVeil = new ResizeVeil(mContext, mDisplayController, mResizeVeilBitmap, mTaskInfo, mTaskSurface, mSurfaceControlTransactionSupplier); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index 9624d46678bf..5379ca6cd51d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -24,7 +24,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERL import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER; -import static com.android.input.flags.Flags.enablePointerChoreographer; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT; import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT; @@ -54,6 +54,7 @@ import android.view.ViewConfiguration; import android.view.WindowManagerGlobal; import android.window.InputTransferToken; +import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; @@ -399,12 +400,17 @@ class DragResizeInputListener implements AutoCloseable { float rawX = e.getRawX(0); float rawY = e.getRawY(0); int ctrlType = mDragResizeWindowGeometry.calculateCtrlType(isTouch, x, y); + ProtoLog.d(WM_SHELL_DESKTOP_MODE, + "%s: Handling action down, update ctrlType to %d", TAG, ctrlType); mDragStartTaskBounds = mCallback.onDragPositioningStart(ctrlType, rawX, rawY); // Increase the input sink region to cover the whole screen; this is to // prevent input and focus from going to other tasks during a drag resize. updateInputSinkRegionForDrag(mDragStartTaskBounds); result = true; + } else { + ProtoLog.d(WM_SHELL_DESKTOP_MODE, + "%s: Handling action down, but ignore event", TAG); } break; } @@ -499,12 +505,10 @@ class DragResizeInputListener implements AutoCloseable { // where views in the task can receive input events because we can't set touch regions // of input sinks to have rounded corners. if (mLastCursorType != cursorType || cursorType != PointerIcon.TYPE_DEFAULT) { - if (enablePointerChoreographer()) { - mInputManager.setPointerIcon(PointerIcon.getSystemIcon(mContext, cursorType), - displayId, deviceId, pointerId, mInputChannel.getToken()); - } else { - mInputManager.setPointerIconType(cursorType); - } + ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: update pointer icon from %d to %d", + TAG, mLastCursorType, cursorType); + mInputManager.setPointerIcon(PointerIcon.getSystemIcon(mContext, cursorType), + displayId, deviceId, pointerId, mInputChannel.getToken()); mLastCursorType = cursorType; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java index eafb56995db7..4f513f0a0fd8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java @@ -33,6 +33,9 @@ import android.graphics.Region; import android.util.Size; import android.view.MotionEvent; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + import com.android.wm.shell.R; import java.util.Objects; @@ -41,6 +44,11 @@ import java.util.Objects; * Geometry for a drag resize region for a particular window. */ final class DragResizeWindowGeometry { + // TODO(b/337264971) clean up when no longer needed + @VisibleForTesting static final boolean DEBUG = true; + // The additional width to apply to edge resize bounds just for logging when a touch is + // close. + @VisibleForTesting static final int EDGE_DEBUG_BUFFER = 15; private final int mTaskCornerRadius; private final Size mTaskSize; // The size of the handle applied to the edges of the window, for the user to drag resize. @@ -51,10 +59,9 @@ final class DragResizeWindowGeometry { // The task corners to permit drag resizing with a fine input, such as stylus or cursor. private final @NonNull TaskCorners mFineTaskCorners; // The bounds for each edge drag region, which can resize the task in one direction. - private final @NonNull Rect mTopEdgeBounds; - private final @NonNull Rect mLeftEdgeBounds; - private final @NonNull Rect mRightEdgeBounds; - private final @NonNull Rect mBottomEdgeBounds; + private final @NonNull TaskEdges mTaskEdges; + // Extra-large edge bounds for logging to help debug when an edge resize is ignored. + private final @Nullable TaskEdges mDebugTaskEdges; DragResizeWindowGeometry(int taskCornerRadius, @NonNull Size taskSize, int resizeHandleThickness, int fineCornerSize, int largeCornerSize) { @@ -66,26 +73,12 @@ final class DragResizeWindowGeometry { mFineTaskCorners = new TaskCorners(mTaskSize, fineCornerSize); // Save touch areas for each edge. - mTopEdgeBounds = new Rect( - -mResizeHandleThickness, - -mResizeHandleThickness, - mTaskSize.getWidth() + mResizeHandleThickness, - 0); - mLeftEdgeBounds = new Rect( - -mResizeHandleThickness, - 0, - 0, - mTaskSize.getHeight()); - mRightEdgeBounds = new Rect( - mTaskSize.getWidth(), - 0, - mTaskSize.getWidth() + mResizeHandleThickness, - mTaskSize.getHeight()); - mBottomEdgeBounds = new Rect( - -mResizeHandleThickness, - mTaskSize.getHeight(), - mTaskSize.getWidth() + mResizeHandleThickness, - mTaskSize.getHeight() + mResizeHandleThickness); + mTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness); + if (DEBUG) { + mDebugTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness + EDGE_DEBUG_BUFFER); + } else { + mDebugTaskEdges = null; + } } /** @@ -127,10 +120,13 @@ final class DragResizeWindowGeometry { */ void union(@NonNull Region region) { // Apply the edge resize regions. - region.union(mTopEdgeBounds); - region.union(mLeftEdgeBounds); - region.union(mRightEdgeBounds); - region.union(mBottomEdgeBounds); + if (inDebugMode()) { + // Use the larger edge sizes if we are debugging, to be able to log if we ignored a + // touch due to the size of the edge region. + mDebugTaskEdges.union(region); + } else { + mTaskEdges.union(region); + } if (enableWindowingEdgeDragResize()) { // Apply the corners as well for the larger corners, to ensure we capture all possible @@ -216,6 +212,10 @@ final class DragResizeWindowGeometry { @DragPositioningCallback.CtrlType private int calculateEdgeResizeCtrlType(float x, float y) { + if (inDebugMode() && (mDebugTaskEdges.contains((int) x, (int) y) + && !mTaskEdges.contains((int) x, (int) y))) { + return CTRL_TYPE_UNDEFINED; + } int ctrlType = CTRL_TYPE_UNDEFINED; // mTaskCornerRadius is only used in comparing with corner regions. Comparisons with // sides will use the bounds specified in setGeometry and not go into task bounds. @@ -306,10 +306,9 @@ final class DragResizeWindowGeometry { && this.mResizeHandleThickness == other.mResizeHandleThickness && this.mFineTaskCorners.equals(other.mFineTaskCorners) && this.mLargeTaskCorners.equals(other.mLargeTaskCorners) - && this.mTopEdgeBounds.equals(other.mTopEdgeBounds) - && this.mLeftEdgeBounds.equals(other.mLeftEdgeBounds) - && this.mRightEdgeBounds.equals(other.mRightEdgeBounds) - && this.mBottomEdgeBounds.equals(other.mBottomEdgeBounds); + && (inDebugMode() + ? this.mDebugTaskEdges.equals(other.mDebugTaskEdges) + : this.mTaskEdges.equals(other.mTaskEdges)); } @Override @@ -320,10 +319,11 @@ final class DragResizeWindowGeometry { mResizeHandleThickness, mFineTaskCorners, mLargeTaskCorners, - mTopEdgeBounds, - mLeftEdgeBounds, - mRightEdgeBounds, - mBottomEdgeBounds); + (inDebugMode() ? mDebugTaskEdges : mTaskEdges)); + } + + private boolean inDebugMode() { + return DEBUG && mDebugTaskEdges != null; } /** @@ -431,4 +431,92 @@ final class DragResizeWindowGeometry { mRightBottomCornerBounds); } } + + /** + * Representation of the drag resize regions at the edges of the window. + */ + private static class TaskEdges { + private final @NonNull Rect mTopEdgeBounds; + private final @NonNull Rect mLeftEdgeBounds; + private final @NonNull Rect mRightEdgeBounds; + private final @NonNull Rect mBottomEdgeBounds; + private final @NonNull Region mRegion; + + private TaskEdges(@NonNull Size taskSize, int resizeHandleThickness) { + // Save touch areas for each edge. + mTopEdgeBounds = new Rect( + -resizeHandleThickness, + -resizeHandleThickness, + taskSize.getWidth() + resizeHandleThickness, + 0); + mLeftEdgeBounds = new Rect( + -resizeHandleThickness, + 0, + 0, + taskSize.getHeight()); + mRightEdgeBounds = new Rect( + taskSize.getWidth(), + 0, + taskSize.getWidth() + resizeHandleThickness, + taskSize.getHeight()); + mBottomEdgeBounds = new Rect( + -resizeHandleThickness, + taskSize.getHeight(), + taskSize.getWidth() + resizeHandleThickness, + taskSize.getHeight() + resizeHandleThickness); + + mRegion = new Region(); + mRegion.union(mTopEdgeBounds); + mRegion.union(mLeftEdgeBounds); + mRegion.union(mRightEdgeBounds); + mRegion.union(mBottomEdgeBounds); + } + + /** + * Returns {@code true} if the edges contain the given point. + */ + private boolean contains(int x, int y) { + return mRegion.contains(x, y); + } + + /** + * Updates the region to include all four corners. + */ + private void union(Region region) { + region.union(mTopEdgeBounds); + region.union(mLeftEdgeBounds); + region.union(mRightEdgeBounds); + region.union(mBottomEdgeBounds); + } + + @Override + public String toString() { + return "TaskEdges for the" + + " top " + mTopEdgeBounds + + " left " + mLeftEdgeBounds + + " right " + mRightEdgeBounds + + " bottom " + mBottomEdgeBounds; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (this == obj) return true; + if (!(obj instanceof TaskEdges other)) return false; + + return this.mTopEdgeBounds.equals(other.mTopEdgeBounds) + && this.mLeftEdgeBounds.equals(other.mLeftEdgeBounds) + && this.mRightEdgeBounds.equals(other.mRightEdgeBounds) + && this.mBottomEdgeBounds.equals(other.mBottomEdgeBounds); + } + + @Override + public int hashCode() { + return Objects.hash( + mTopEdgeBounds, + mLeftEdgeBounds, + mRightEdgeBounds, + mBottomEdgeBounds); + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt index a2293d53618a..ec204714c341 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.windowdecor.extension import android.app.TaskInfo -import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS import android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND @@ -36,6 +35,3 @@ val TaskInfo.isLightCaptionBarAppearance: Boolean val TaskInfo.isFullscreen: Boolean get() = windowingMode == WINDOWING_MODE_FULLSCREEN - -val TaskInfo.isFreeform: Boolean - get() = windowingMode == WINDOWING_MODE_FREEFORM diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java index 6be411dd81d0..0f433770777e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java @@ -1193,23 +1193,6 @@ public class BubbleDataTest extends ShellTestCase { } @Test - public void test_removeOverflowBubble() { - sendUpdatedEntryAtTime(mEntryA1, 2000); - mBubbleData.setListener(mListener); - - mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE); - verifyUpdateReceived(); - assertOverflowChangedTo(ImmutableList.of(mBubbleA1)); - - mBubbleData.removeOverflowBubble(mBubbleA1); - verifyUpdateReceived(); - - BubbleData.Update update = mUpdateCaptor.getValue(); - assertThat(update.removedOverflowBubble).isEqualTo(mBubbleA1); - assertOverflowChangedTo(ImmutableList.of()); - } - - @Test public void test_getInitialStateForBubbleBar_includesInitialBubblesAndPosition() { sendUpdatedEntryAtTime(mEntryA1, 1000); sendUpdatedEntryAtTime(mEntryA2, 2000); @@ -1235,6 +1218,51 @@ public class BubbleDataTest extends ShellTestCase { assertExpandedChangedTo(true); } + @Test + public void testShowOverflowChanged_hasOverflowBubbles() { + assertThat(mBubbleData.getOverflowBubbles()).isEmpty(); + sendUpdatedEntryAtTime(mEntryA1, 1000); + mBubbleData.setListener(mListener); + + mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE); + verifyUpdateReceived(); + assertThat(mUpdateCaptor.getValue().showOverflowChanged).isTrue(); + assertThat(mBubbleData.getOverflowBubbles()).isNotEmpty(); + } + + @Test + public void testShowOverflowChanged_false_hasOverflowBubbles() { + assertThat(mBubbleData.getOverflowBubbles()).isEmpty(); + sendUpdatedEntryAtTime(mEntryA1, 1000); + sendUpdatedEntryAtTime(mEntryA2, 1000); + mBubbleData.setListener(mListener); + + // First overflowed causes change event + mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE); + verifyUpdateReceived(); + assertThat(mUpdateCaptor.getValue().showOverflowChanged).isTrue(); + assertThat(mBubbleData.getOverflowBubbles()).isNotEmpty(); + + // Second overflow does not + mBubbleData.dismissBubbleWithKey(mEntryA2.getKey(), Bubbles.DISMISS_USER_GESTURE); + verifyUpdateReceived(); + assertThat(mUpdateCaptor.getValue().showOverflowChanged).isFalse(); + } + + @Test + public void testShowOverflowChanged_noOverflowBubbles() { + sendUpdatedEntryAtTime(mEntryA1, 1000); + mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_USER_GESTURE); + assertThat(mBubbleData.getOverflowBubbles()).isNotEmpty(); + mBubbleData.setListener(mListener); + + mBubbleData.dismissBubbleWithKey(mEntryA1.getKey(), Bubbles.DISMISS_NOTIF_CANCEL); + + verifyUpdateReceived(); + assertThat(mUpdateCaptor.getValue().showOverflowChanged).isTrue(); + assertThat(mBubbleData.getOverflowBubbles()).isEmpty(); + } + private void verifyUpdateReceived() { verify(mListener).applyUpdate(mUpdateCaptor.capture()); reset(mListener); 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 afae653f0682..9c008647104a 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 @@ -668,6 +668,18 @@ public class CompatUIControllerTest extends ShellTestCase { Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); } + @Test + public void testLetterboxEduLayout_notCreatedWhenLetterboxEducationIsDisabled() { + TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, + CAMERA_COMPAT_CONTROL_HIDDEN); + taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled = false; + + mController.onCompatInfoChanged(taskInfo, mMockTaskListener); + + verify(mController, never()).createLetterboxEduWindowManager(any(), eq(taskInfo), + eq(mMockTaskListener)); + } + private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat, @CameraCompatControlState int cameraCompatControlState) { return createTaskInfo(displayId, taskId, hasSizeCompat, cameraCompatControlState, @@ -694,6 +706,8 @@ public class CompatUIControllerTest extends ShellTestCase { taskInfo.isVisible = isVisible; taskInfo.isFocused = isFocused; taskInfo.isTopActivityTransparent = isTopActivityTransparent; + taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled = true; + taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed = true; return taskInfo; } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt index dca7be12fffc..8f59f30da697 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt @@ -182,18 +182,6 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { } @Test - fun addListener_notifiesStashed() { - repo.setStashed(DEFAULT_DISPLAY, true) - val listener = TestVisibilityListener() - val executor = TestShellExecutor() - repo.addVisibleTasksListener(listener, executor) - executor.flushAll() - - assertThat(listener.stashedOnDefaultDisplay).isTrue() - assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1) - } - - @Test fun addListener_tasksOnDifferentDisplay_doesNotNotify() { repo.updateVisibleFreeformTasks(SECOND_DISPLAY, taskId = 1, visible = true) val listener = TestVisibilityListener() @@ -400,65 +388,6 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { } @Test - fun setStashed_stateIsUpdatedForTheDisplay() { - repo.setStashed(DEFAULT_DISPLAY, true) - assertThat(repo.isStashed(DEFAULT_DISPLAY)).isTrue() - assertThat(repo.isStashed(SECOND_DISPLAY)).isFalse() - - repo.setStashed(DEFAULT_DISPLAY, false) - assertThat(repo.isStashed(DEFAULT_DISPLAY)).isFalse() - } - - @Test - fun setStashed_notifyListener() { - val listener = TestVisibilityListener() - val executor = TestShellExecutor() - repo.addVisibleTasksListener(listener, executor) - repo.setStashed(DEFAULT_DISPLAY, true) - executor.flushAll() - assertThat(listener.stashedOnDefaultDisplay).isTrue() - assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1) - - repo.setStashed(DEFAULT_DISPLAY, false) - executor.flushAll() - assertThat(listener.stashedOnDefaultDisplay).isFalse() - assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(2) - } - - @Test - fun setStashed_secondCallDoesNotNotify() { - val listener = TestVisibilityListener() - val executor = TestShellExecutor() - repo.addVisibleTasksListener(listener, executor) - repo.setStashed(DEFAULT_DISPLAY, true) - repo.setStashed(DEFAULT_DISPLAY, true) - executor.flushAll() - assertThat(listener.stashedChangesOnDefaultDisplay).isEqualTo(1) - } - - @Test - fun setStashed_tracksPerDisplay() { - val listener = TestVisibilityListener() - val executor = TestShellExecutor() - repo.addVisibleTasksListener(listener, executor) - - repo.setStashed(DEFAULT_DISPLAY, true) - executor.flushAll() - assertThat(listener.stashedOnDefaultDisplay).isTrue() - assertThat(listener.stashedOnSecondaryDisplay).isFalse() - - repo.setStashed(SECOND_DISPLAY, true) - executor.flushAll() - assertThat(listener.stashedOnDefaultDisplay).isTrue() - assertThat(listener.stashedOnSecondaryDisplay).isTrue() - - repo.setStashed(DEFAULT_DISPLAY, false) - executor.flushAll() - assertThat(listener.stashedOnDefaultDisplay).isFalse() - assertThat(listener.stashedOnSecondaryDisplay).isTrue() - } - - @Test fun removeFreeformTask_removesTaskBoundsBeforeMaximize() { val taskId = 1 repo.saveBoundsBeforeMaximize(taskId, Rect(0, 0, 200, 200)) @@ -598,12 +527,6 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { var visibleChangesOnDefaultDisplay = 0 var visibleChangesOnSecondaryDisplay = 0 - var stashedOnDefaultDisplay = false - var stashedOnSecondaryDisplay = false - - var stashedChangesOnDefaultDisplay = 0 - var stashedChangesOnSecondaryDisplay = 0 - override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) { when (displayId) { DEFAULT_DISPLAY -> { @@ -617,20 +540,6 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { else -> fail("Visible task listener received unexpected display id: $displayId") } } - - override fun onStashedChanged(displayId: Int, stashed: Boolean) { - when (displayId) { - DEFAULT_DISPLAY -> { - stashedOnDefaultDisplay = stashed - stashedChangesOnDefaultDisplay++ - } - SECOND_DISPLAY -> { - stashedOnSecondaryDisplay = stashed - stashedChangesOnDefaultDisplay++ - } - else -> fail("Visible task listener received unexpected display id: $displayId") - } - } } companion object { 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 df8a2223dadc..7e55628b5641 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 @@ -24,6 +24,12 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.content.Intent +import android.content.pm.ActivityInfo +import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE +import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT +import android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED +import android.content.res.Configuration.ORIENTATION_LANDSCAPE +import android.content.res.Configuration.ORIENTATION_PORTRAIT import android.graphics.Point import android.graphics.PointF import android.graphics.Rect @@ -101,7 +107,9 @@ import org.mockito.Mockito.any import org.mockito.Mockito.anyInt import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.mock +import org.mockito.Mockito.spy import org.mockito.Mockito.verify +import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.atLeastOnce import org.mockito.kotlin.capture import org.mockito.quality.Strictness @@ -141,6 +149,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var dragAndDropController: DragAndDropController @Mock lateinit var multiInstanceHelper: MultiInstanceHelper @Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver + @Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator private lateinit var mockitoSession: StaticMockitoSession private lateinit var controller: DesktopTasksController @@ -154,6 +163,15 @@ class DesktopTasksControllerTest : ShellTestCase() { // Mock running tasks are registered here so we can get the list from mock shell task organizer private val runningTasks = mutableListOf<RunningTaskInfo>() + private val DISPLAY_DIMENSION_SHORT = 1600 + private val DISPLAY_DIMENSION_LONG = 2560 + private val DEFAULT_LANDSCAPE_BOUNDS = Rect(320, 200, 2240, 1400) + private val DEFAULT_PORTRAIT_BOUNDS = Rect(200, 320, 1400, 2240) + private val RESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 680, 1575, 1880) + private val RESIZABLE_PORTRAIT_BOUNDS = Rect(680, 200, 1880, 1400) + private val UNRESIZABLE_LANDSCAPE_BOUNDS = Rect(25, 699, 1575, 1861) + private val UNRESIZABLE_PORTRAIT_BOUNDS = Rect(830, 200, 1730, 1400) + @Before fun setUp() { mockitoSession = mockitoSession().strictness(Strictness.LENIENT) @@ -161,7 +179,7 @@ class DesktopTasksControllerTest : ShellTestCase() { whenever(DesktopModeStatus.isEnabled()).thenReturn(true) doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } - shellInit = Mockito.spy(ShellInit(testExecutor)) + shellInit = spy(ShellInit(testExecutor)) desktopModeTaskRepository = DesktopModeTaskRepository() desktopTasksLimiter = DesktopTasksLimiter(transitions, desktopModeTaskRepository, shellTaskOrganizer) @@ -464,6 +482,135 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun moveToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = setUpFullscreenTask() + setUpLandscapeDisplay() + + controller.moveToDesktop(task) + val wct = getLatestMoveToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun moveToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE) + setUpLandscapeDisplay() + + controller.moveToDesktop(task) + val wct = getLatestMoveToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun moveToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT, + shouldLetterbox = true) + setUpLandscapeDisplay() + + controller.moveToDesktop(task) + val wct = getLatestMoveToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun moveToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = setUpFullscreenTask(isResizable = false, + screenOrientation = SCREEN_ORIENTATION_LANDSCAPE) + setUpLandscapeDisplay() + + controller.moveToDesktop(task) + val wct = getLatestMoveToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun moveToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = setUpFullscreenTask(isResizable = false, + screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true) + setUpLandscapeDisplay() + + controller.moveToDesktop(task) + val wct = getLatestMoveToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun moveToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT) + setUpPortraitDisplay() + + controller.moveToDesktop(task) + val wct = getLatestMoveToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun moveToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT, + screenOrientation = SCREEN_ORIENTATION_PORTRAIT) + setUpPortraitDisplay() + + controller.moveToDesktop(task) + val wct = getLatestMoveToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun moveToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT, + screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true) + setUpPortraitDisplay() + + controller.moveToDesktop(task) + val wct = getLatestMoveToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun moveToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = setUpFullscreenTask(isResizable = false, + deviceOrientation = ORIENTATION_PORTRAIT, + screenOrientation = SCREEN_ORIENTATION_PORTRAIT) + setUpPortraitDisplay() + + controller.moveToDesktop(task) + val wct = getLatestMoveToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun moveToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = setUpFullscreenTask(isResizable = false, + deviceOrientation = ORIENTATION_PORTRAIT, + screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true) + setUpPortraitDisplay() + + controller.moveToDesktop(task) + val wct = getLatestMoveToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS) + } + + @Test fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() { val task = setUpFullscreenTask() val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!! @@ -897,29 +1044,6 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_fullscreenTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() { - assumeTrue(ENABLE_SHELL_TRANSITIONS) - whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) - - val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY) - markTaskHidden(stashedFreeformTask) - - val fullscreenTask = createFullscreenTask(DEFAULT_DISPLAY) - - controller.stashDesktopApps(DEFAULT_DISPLAY) - - val result = controller.handleRequest(Binder(), createTransition(fullscreenTask)) - assertThat(result).isNotNull() - result!!.assertReorderSequence(stashedFreeformTask, fullscreenTask) - assertThat(result.changes[fullscreenTask.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FREEFORM) - - // Stashed state should be cleared - assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse() - } - - @Test fun handleRequest_freeformTask_freeformVisible_returnNull() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -986,27 +1110,6 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_freeformTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() { - assumeTrue(ENABLE_SHELL_TRANSITIONS) - whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) - - val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY) - markTaskHidden(stashedFreeformTask) - - val freeformTask = createFreeformTask(DEFAULT_DISPLAY) - - controller.stashDesktopApps(DEFAULT_DISPLAY) - - val result = controller.handleRequest(Binder(), createTransition(freeformTask)) - assertThat(result).isNotNull() - result?.assertReorderSequence(stashedFreeformTask, freeformTask) - - // Stashed state should be cleared - assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse() - } - - @Test fun handleRequest_notOpenOrToFrontTransition_returnNull() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -1122,29 +1225,6 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun stashDesktopApps_stateUpdates() { - whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) - - controller.stashDesktopApps(DEFAULT_DISPLAY) - - assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isTrue() - assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isFalse() - } - - @Test - fun hideStashedDesktopApps_stateUpdates() { - whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) - - desktopModeTaskRepository.setStashed(DEFAULT_DISPLAY, true) - desktopModeTaskRepository.setStashed(SECOND_DISPLAY, true) - controller.hideStashedDesktopApps(DEFAULT_DISPLAY) - - assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse() - // Check that second display is not affected - assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isTrue() - } - - @Test fun desktopTasksVisibilityChange_visible_setLaunchAdjacentDisabled() { val task = setUpFreeformTask() clearInvocations(launchAdjacentController) @@ -1225,6 +1305,185 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = setUpFullscreenTask() + setUpLandscapeDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE) + setUpLandscapeDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT, + shouldLetterbox = true) + setUpLandscapeDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = setUpFullscreenTask(isResizable = false, + screenOrientation = SCREEN_ORIENTATION_LANDSCAPE) + setUpLandscapeDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_LANDSCAPE_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = setUpFullscreenTask(isResizable = false, + screenOrientation = SCREEN_ORIENTATION_PORTRAIT, shouldLetterbox = true) + setUpLandscapeDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT) + setUpPortraitDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT, + screenOrientation = SCREEN_ORIENTATION_PORTRAIT) + setUpPortraitDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT, + screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true) + setUpPortraitDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(RESIZABLE_LANDSCAPE_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = setUpFullscreenTask(isResizable = false, + deviceOrientation = ORIENTATION_PORTRAIT, + screenOrientation = SCREEN_ORIENTATION_PORTRAIT) + setUpPortraitDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(800f, 1280f), task) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(DEFAULT_PORTRAIT_BOUNDS) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS) + fun dragToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() { + doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val spyController = spy(controller) + whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator) + whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull())) + .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR) + + val task = setUpFullscreenTask(isResizable = false, + deviceOrientation = ORIENTATION_PORTRAIT, + screenOrientation = SCREEN_ORIENTATION_LANDSCAPE, shouldLetterbox = true) + setUpPortraitDisplay() + + spyController.onDragPositioningEndThroughStatusBar(PointF(200f, 200f), task) + val wct = getLatestDragToDesktopWct() + assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS) + } + + @Test fun onDesktopDragMove_endsOutsideValidDragArea_snapsToValidBounds() { val task = setUpFreeformTask() val mockSurface = mock(SurfaceControl::class.java) @@ -1276,8 +1535,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.toggleDesktopTaskSize(task) // Assert bounds set to stable bounds val wct = getLatestToggleResizeDesktopTaskWct() - assertThat(wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds) - .isEqualTo(STABLE_BOUNDS) + assertThat(findBoundsChange(wct, task)).isEqualTo(STABLE_BOUNDS) } @Test @@ -1304,8 +1562,7 @@ class DesktopTasksControllerTest : ShellTestCase() { // Assert bounds set to last bounds before maximize val wct = getLatestToggleResizeDesktopTaskWct() - assertThat(wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds) - .isEqualTo(boundsBeforeMaximize) + assertThat(findBoundsChange(wct, task)).isEqualTo(boundsBeforeMaximize) } @Test @@ -1346,14 +1603,65 @@ class DesktopTasksControllerTest : ShellTestCase() { return task } - private fun setUpFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { + private fun setUpFullscreenTask( + displayId: Int = DEFAULT_DISPLAY, + isResizable: Boolean = true, + windowingMode: Int = WINDOWING_MODE_FULLSCREEN, + deviceOrientation: Int = ORIENTATION_LANDSCAPE, + screenOrientation: Int = SCREEN_ORIENTATION_UNSPECIFIED, + shouldLetterbox: Boolean = false + ): RunningTaskInfo { val task = createFullscreenTask(displayId) + val activityInfo = ActivityInfo() + activityInfo.screenOrientation = screenOrientation + with(task) { + topActivityInfo = activityInfo + isResizeable = isResizable + configuration.orientation = deviceOrientation + configuration.windowConfiguration.windowingMode = windowingMode + + if (shouldLetterbox) { + if (deviceOrientation == ORIENTATION_LANDSCAPE && + screenOrientation == SCREEN_ORIENTATION_PORTRAIT) { + // Letterbox to portrait size + appCompatTaskInfo.topActivityBoundsLetterboxed = true + appCompatTaskInfo.topActivityLetterboxWidth = 1200 + appCompatTaskInfo.topActivityLetterboxHeight = 1600 + } else if (deviceOrientation == ORIENTATION_PORTRAIT && + screenOrientation == SCREEN_ORIENTATION_LANDSCAPE) { + // Letterbox to landscape size + appCompatTaskInfo.topActivityBoundsLetterboxed = true + appCompatTaskInfo.topActivityLetterboxWidth = 1600 + appCompatTaskInfo.topActivityLetterboxHeight = 1200 + } + } else { + appCompatTaskInfo.topActivityBoundsLetterboxed = false + } + + if (deviceOrientation == ORIENTATION_LANDSCAPE) { + configuration.windowConfiguration.appBounds = Rect(0, 0, + DISPLAY_DIMENSION_LONG, DISPLAY_DIMENSION_SHORT) + } else { + configuration.windowConfiguration.appBounds = Rect(0, 0, + DISPLAY_DIMENSION_SHORT, DISPLAY_DIMENSION_LONG) + } + } whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) runningTasks.add(task) return task } + private fun setUpLandscapeDisplay() { + whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_LONG) + whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_SHORT) + } + + private fun setUpPortraitDisplay() { + whenever(displayLayout.width()).thenReturn(DISPLAY_DIMENSION_SHORT) + whenever(displayLayout.height()).thenReturn(DISPLAY_DIMENSION_LONG) + } + private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { val task = createSplitScreenTask(displayId) whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true) @@ -1418,6 +1726,17 @@ class DesktopTasksControllerTest : ShellTestCase() { return arg.value } + private fun getLatestDragToDesktopWct(): WindowContainerTransaction { + val arg: ArgumentCaptor<WindowContainerTransaction> = + ArgumentCaptor.forClass(WindowContainerTransaction::class.java) + if (ENABLE_SHELL_TRANSITIONS) { + verify(dragToDesktopTransitionHandler).finishDragToDesktopTransition(capture(arg)) + } else { + verify(shellTaskOrganizer).applyTransaction(capture(arg)) + } + return arg.value + } + private fun getLatestExitDesktopWct(): WindowContainerTransaction { val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java) if (ENABLE_SHELL_TRANSITIONS) { @@ -1429,6 +1748,10 @@ class DesktopTasksControllerTest : ShellTestCase() { return arg.value } + private fun findBoundsChange(wct: WindowContainerTransaction, task: RunningTaskInfo): Rect? = + wct.changes[task.token.asBinder()]?.configuration?.windowConfiguration?.bounds + + private fun verifyWCTNotExecuted() { if (ENABLE_SHELL_TRANSITIONS) { verify(transitions, never()).startTransition(anyInt(), any(), isNull()) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java index 82e5a1cd25ce..54645083eca8 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java @@ -56,6 +56,8 @@ public class DragResizeWindowGeometryTests { private static final Size TASK_SIZE = new Size(500, 1000); private static final int TASK_CORNER_RADIUS = 10; private static final int EDGE_RESIZE_THICKNESS = 15; + private static final int EDGE_RESIZE_DEBUG_THICKNESS = EDGE_RESIZE_THICKNESS + + (DragResizeWindowGeometry.DEBUG ? DragResizeWindowGeometry.EDGE_DEBUG_BUFFER : 0); private static final int FINE_CORNER_SIZE = EDGE_RESIZE_THICKNESS * 2 + 10; private static final int LARGE_CORNER_SIZE = FINE_CORNER_SIZE + 10; private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry( @@ -90,13 +92,14 @@ public class DragResizeWindowGeometryTests { EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE), new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE)) - .addEqualityGroup( + .addEqualityGroup(new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, + EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5), new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, - EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, - LARGE_CORNER_SIZE + 5), + EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE + 5)) + .addEqualityGroup(new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, + EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE + 4, LARGE_CORNER_SIZE), new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE, - EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, - LARGE_CORNER_SIZE + 5)) + EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE + 4, LARGE_CORNER_SIZE)) .testEquals(); } @@ -122,21 +125,21 @@ public class DragResizeWindowGeometryTests { private static void verifyHorizontalEdge(@NonNull Region region, @NonNull Point point) { assertThat(region.contains(point.x, point.y)).isTrue(); // Horizontally along the edge is still contained. - assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isTrue(); - assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isTrue(); + assertThat(region.contains(point.x + EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isTrue(); + assertThat(region.contains(point.x - EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isTrue(); // Vertically along the edge is not contained. - assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isFalse(); - assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isFalse(); + assertThat(region.contains(point.x, point.y - EDGE_RESIZE_DEBUG_THICKNESS)).isFalse(); + assertThat(region.contains(point.x, point.y + EDGE_RESIZE_DEBUG_THICKNESS)).isFalse(); } private static void verifyVerticalEdge(@NonNull Region region, @NonNull Point point) { assertThat(region.contains(point.x, point.y)).isTrue(); // Horizontally along the edge is not contained. - assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isFalse(); - assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isFalse(); + assertThat(region.contains(point.x + EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isFalse(); + assertThat(region.contains(point.x - EDGE_RESIZE_DEBUG_THICKNESS, point.y)).isFalse(); // Vertically along the edge is contained. - assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isTrue(); - assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isTrue(); + assertThat(region.contains(point.x, point.y - EDGE_RESIZE_DEBUG_THICKNESS)).isTrue(); + assertThat(region.contains(point.x, point.y + EDGE_RESIZE_DEBUG_THICKNESS)).isTrue(); } /** @@ -148,7 +151,10 @@ public class DragResizeWindowGeometryTests { public void testRegionUnion_edgeDragResizeEnabled_containsLargeCorners() { Region region = new Region(); GEOMETRY.union(region); - final int cornerRadius = LARGE_CORNER_SIZE / 2; + // Make sure we're choosing a point outside of any debug region buffer. + final int cornerRadius = DragResizeWindowGeometry.DEBUG + ? Math.max(LARGE_CORNER_SIZE / 2, EDGE_RESIZE_DEBUG_THICKNESS) + : LARGE_CORNER_SIZE / 2; new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region); } @@ -162,7 +168,9 @@ public class DragResizeWindowGeometryTests { public void testRegionUnion_edgeDragResizeDisabled_containsFineCorners() { Region region = new Region(); GEOMETRY.union(region); - final int cornerRadius = FINE_CORNER_SIZE / 2; + final int cornerRadius = DragResizeWindowGeometry.DEBUG + ? Math.max(LARGE_CORNER_SIZE / 2, EDGE_RESIZE_DEBUG_THICKNESS) + : LARGE_CORNER_SIZE / 2; new TestPoints(TASK_SIZE, cornerRadius).validateRegion(region); } diff --git a/libs/androidfw/fuzz/resourcefile_fuzzer/Android.bp b/libs/androidfw/fuzz/resourcefile_fuzzer/Android.bp index b511244c4a30..619658923865 100644 --- a/libs/androidfw/fuzz/resourcefile_fuzzer/Android.bp +++ b/libs/androidfw/fuzz/resourcefile_fuzzer/Android.bp @@ -19,6 +19,7 @@ package { // to get the below license kinds: // SPDX-license-identifier-Apache-2.0 default_applicable_licenses: ["frameworks_base_libs_androidfw_license"], + default_team: "trendy_team_android_resources", } cc_fuzz { @@ -31,7 +32,7 @@ cc_fuzz { static_libs: ["libgmock"], target: { android: { - shared_libs:[ + shared_libs: [ "libandroidfw", "libbase", "libcutils", @@ -52,4 +53,15 @@ cc_fuzz { ], }, }, + fuzz_config: { + cc: [ + "android-resources@google.com", + ], + componentid: 568761, + description: "The fuzzer targets the APIs of libandroidfw", + vector: "local_no_privileges_required", + service_privilege: "privileged", + users: "multi_user", + fuzzed_code_usage: "shipped", + }, } diff --git a/libs/hwui/ColorFilter.h b/libs/hwui/ColorFilter.h index 1a5b938d6eed..31c9db7ca4fb 100644 --- a/libs/hwui/ColorFilter.h +++ b/libs/hwui/ColorFilter.h @@ -23,17 +23,42 @@ #include "GraphicsJNI.h" #include "SkColorFilter.h" -#include "SkiaWrapper.h" namespace android { namespace uirenderer { -class ColorFilter : public SkiaWrapper<SkColorFilter> { +class ColorFilter : public VirtualLightRefBase { public: static ColorFilter* fromJava(jlong handle) { return reinterpret_cast<ColorFilter*>(handle); } + sk_sp<SkColorFilter> getInstance() { + if (mInstance != nullptr && shouldDiscardInstance()) { + mInstance = nullptr; + } + + if (mInstance == nullptr) { + mInstance = createInstance(); + if (mInstance) { + mInstance = mInstance->makeWithWorkingColorSpace(SkColorSpace::MakeSRGB()); + } + mGenerationId++; + } + return mInstance; + } + + virtual bool shouldDiscardInstance() const { return false; } + + void discardInstance() { mInstance = nullptr; } + + [[nodiscard]] int32_t getGenerationId() const { return mGenerationId; } + protected: ColorFilter() = default; + virtual sk_sp<SkColorFilter> createInstance() = 0; + +private: + sk_sp<SkColorFilter> mInstance = nullptr; + int32_t mGenerationId = 0; }; class BlendModeColorFilter : public ColorFilter { diff --git a/libs/hwui/SkiaWrapper.h b/libs/hwui/SkiaWrapper.h deleted file mode 100644 index bd0e35aadbb4..000000000000 --- a/libs/hwui/SkiaWrapper.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef SKIA_WRAPPER_H_ -#define SKIA_WRAPPER_H_ - -#include <SkRefCnt.h> -#include <utils/RefBase.h> - -namespace android::uirenderer { - -template <typename T> -class SkiaWrapper : public VirtualLightRefBase { -public: - sk_sp<T> getInstance() { - if (mInstance != nullptr && shouldDiscardInstance()) { - mInstance = nullptr; - } - - if (mInstance == nullptr) { - mInstance = createInstance(); - mGenerationId++; - } - return mInstance; - } - - virtual bool shouldDiscardInstance() const { return false; } - - void discardInstance() { mInstance = nullptr; } - - [[nodiscard]] int32_t getGenerationId() const { return mGenerationId; } - -protected: - virtual sk_sp<T> createInstance() = 0; - -private: - sk_sp<T> mInstance = nullptr; - int32_t mGenerationId = 0; -}; - -} // namespace android::uirenderer - -#endif // SKIA_WRAPPER_H_ diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp index 6a465442c2b4..5cf5a1d00815 100644 --- a/libs/input/MouseCursorController.cpp +++ b/libs/input/MouseCursorController.cpp @@ -117,7 +117,7 @@ FloatPoint MouseCursorController::getPosition() const { return {mLocked.pointerX, mLocked.pointerY}; } -int32_t MouseCursorController::getDisplayId() const { +ui::LogicalDisplayId MouseCursorController::getDisplayId() const { std::scoped_lock lock(mLock); return mLocked.viewport.displayId; } @@ -467,10 +467,10 @@ void MouseCursorController::startAnimationLocked() REQUIRES(mLock) { std::function<bool(nsecs_t)> func = std::bind(&MouseCursorController::doAnimations, this, _1); /* - * Using -1 for displayId here to avoid removing the callback + * Using ui::ADISPLAY_ID_NONE for displayId here to avoid removing the callback * if a TouchSpotController with the same display is removed. */ - mContext.addAnimationCallback(-1, func); + mContext.addAnimationCallback(ui::ADISPLAY_ID_NONE, func); } } // namespace android diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h index 00dc0854440e..dc7e8ca16c8a 100644 --- a/libs/input/MouseCursorController.h +++ b/libs/input/MouseCursorController.h @@ -47,7 +47,7 @@ public: void move(float deltaX, float deltaY); void setPosition(float x, float y); FloatPoint getPosition() const; - int32_t getDisplayId() const; + ui::LogicalDisplayId getDisplayId() const; void fade(PointerControllerInterface::Transition transition); void unfade(PointerControllerInterface::Transition transition); void setDisplayViewport(const DisplayViewport& viewport, bool getAdditionalMouseResources); diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index f97992f7c9d1..cca1b07c3118 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -142,7 +142,7 @@ std::optional<FloatRect> PointerController::getBounds() const { } void PointerController::move(float deltaX, float deltaY) { - const int32_t displayId = mCursorController.getDisplayId(); + const ui::LogicalDisplayId displayId = mCursorController.getDisplayId(); vec2 transformed; { std::scoped_lock lock(getLock()); @@ -153,7 +153,7 @@ void PointerController::move(float deltaX, float deltaY) { } void PointerController::setPosition(float x, float y) { - const int32_t displayId = mCursorController.getDisplayId(); + const ui::LogicalDisplayId displayId = mCursorController.getDisplayId(); vec2 transformed; { std::scoped_lock lock(getLock()); @@ -164,7 +164,7 @@ void PointerController::setPosition(float x, float y) { } FloatPoint PointerController::getPosition() const { - const int32_t displayId = mCursorController.getDisplayId(); + const ui::LogicalDisplayId displayId = mCursorController.getDisplayId(); const auto p = mCursorController.getPosition(); { std::scoped_lock lock(getLock()); @@ -173,7 +173,7 @@ FloatPoint PointerController::getPosition() const { } } -int32_t PointerController::getDisplayId() const { +ui::LogicalDisplayId PointerController::getDisplayId() const { return mCursorController.getDisplayId(); } @@ -202,7 +202,7 @@ void PointerController::setPresentation(Presentation presentation) { } void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, - BitSet32 spotIdBits, int32_t displayId) { + BitSet32 spotIdBits, ui::LogicalDisplayId displayId) { std::scoped_lock lock(getLock()); std::array<PointerCoords, MAX_POINTERS> outSpotCoords{}; const ui::Transform& transform = getTransformForDisplayLocked(displayId); @@ -286,7 +286,7 @@ void PointerController::setCustomPointerIcon(const SpriteIcon& icon) { mCursorController.setCustomPointerIcon(icon); } -void PointerController::setSkipScreenshot(int32_t displayId, bool skip) { +void PointerController::setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) { std::scoped_lock lock(getLock()); if (skip) { mLocked.displaysToSkipScreenshot.insert(displayId); @@ -300,14 +300,14 @@ void PointerController::doInactivityTimeout() { } void PointerController::onDisplayViewportsUpdated(const std::vector<DisplayViewport>& viewports) { - std::unordered_set<int32_t> displayIdSet; + std::unordered_set<ui::LogicalDisplayId> displayIdSet; for (const DisplayViewport& viewport : viewports) { displayIdSet.insert(viewport.displayId); } std::scoped_lock lock(getLock()); for (auto it = mLocked.spotControllers.begin(); it != mLocked.spotControllers.end();) { - int32_t displayId = it->first; + ui::LogicalDisplayId displayId = it->first; if (!displayIdSet.count(displayId)) { /* * Ensures that an in-progress animation won't dereference @@ -326,7 +326,8 @@ void PointerController::onDisplayInfosChangedLocked( mLocked.mDisplayInfos = displayInfo; } -const ui::Transform& PointerController::getTransformForDisplayLocked(int displayId) const { +const ui::Transform& PointerController::getTransformForDisplayLocked( + ui::LogicalDisplayId displayId) const { const auto& di = mLocked.mDisplayInfos; auto it = std::find_if(di.begin(), di.end(), [displayId](const gui::DisplayInfo& info) { return info.displayId == displayId; @@ -339,7 +340,8 @@ std::string PointerController::dump() { std::scoped_lock lock(getLock()); dump += StringPrintf(INDENT2 "Presentation: %s\n", ftl::enum_string(mLocked.presentation).c_str()); - dump += StringPrintf(INDENT2 "Pointer Display ID: %" PRIu32 "\n", mLocked.pointerDisplayId); + dump += StringPrintf(INDENT2 "Pointer Display ID: %s\n", + mLocked.pointerDisplayId.toString().c_str()); dump += StringPrintf(INDENT2 "Viewports:\n"); for (const auto& info : mLocked.mDisplayInfos) { info.dump(dump, INDENT3); diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index eaf34d527396..70e5c2455d81 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -55,18 +55,18 @@ public: void move(float deltaX, float deltaY) override; void setPosition(float x, float y) override; FloatPoint getPosition() const override; - int32_t getDisplayId() const override; + ui::LogicalDisplayId getDisplayId() const override; void fade(Transition transition) override; void unfade(Transition transition) override; void setDisplayViewport(const DisplayViewport& viewport) override; void setPresentation(Presentation presentation) override; void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, - BitSet32 spotIdBits, int32_t displayId) override; + BitSet32 spotIdBits, ui::LogicalDisplayId displayId) override; void clearSpots() override; void updatePointerIcon(PointerIconStyle iconId) override; void setCustomPointerIcon(const SpriteIcon& icon) override; - void setSkipScreenshot(int32_t displayId, bool skip) override; + void setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) override; virtual void setInactivityTimeout(InactivityTimeout inactivityTimeout); void doInactivityTimeout(); @@ -109,11 +109,11 @@ private: struct Locked { Presentation presentation; - int32_t pointerDisplayId = ADISPLAY_ID_NONE; + ui::LogicalDisplayId pointerDisplayId = ui::ADISPLAY_ID_NONE; std::vector<gui::DisplayInfo> mDisplayInfos; - std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers; - std::unordered_set<int32_t /* displayId */> displaysToSkipScreenshot; + std::unordered_map<ui::LogicalDisplayId, TouchSpotController> spotControllers; + std::unordered_set<ui::LogicalDisplayId> displaysToSkipScreenshot; } mLocked GUARDED_BY(getLock()); class DisplayInfoListener : public gui::WindowInfosListener { @@ -132,7 +132,8 @@ private: sp<DisplayInfoListener> mDisplayInfoListener; const WindowListenerUnregisterConsumer mUnregisterWindowInfosListener; - const ui::Transform& getTransformForDisplayLocked(int displayId) const REQUIRES(getLock()); + const ui::Transform& getTransformForDisplayLocked(ui::LogicalDisplayId displayId) const + REQUIRES(getLock()); void clearSpotsLocked() REQUIRES(getLock()); }; @@ -148,7 +149,7 @@ public: void setPresentation(Presentation) override { LOG_ALWAYS_FATAL("Should not be called"); } - void setSpots(const PointerCoords*, const uint32_t*, BitSet32, int32_t) override { + void setSpots(const PointerCoords*, const uint32_t*, BitSet32, ui::LogicalDisplayId) override { LOG_ALWAYS_FATAL("Should not be called"); } void clearSpots() override { @@ -176,7 +177,7 @@ public: FloatPoint getPosition() const override { LOG_ALWAYS_FATAL("Should not be called"); } - int32_t getDisplayId() const override { + ui::LogicalDisplayId getDisplayId() const override { LOG_ALWAYS_FATAL("Should not be called"); } void fade(Transition) override { @@ -212,7 +213,7 @@ public: void setPresentation(Presentation) override { LOG_ALWAYS_FATAL("Should not be called"); } - void setSpots(const PointerCoords*, const uint32_t*, BitSet32, int32_t) override { + void setSpots(const PointerCoords*, const uint32_t*, BitSet32, ui::LogicalDisplayId) override { LOG_ALWAYS_FATAL("Should not be called"); } void clearSpots() override { diff --git a/libs/input/PointerControllerContext.cpp b/libs/input/PointerControllerContext.cpp index 15c35176afce..747eb8e5ad1b 100644 --- a/libs/input/PointerControllerContext.cpp +++ b/libs/input/PointerControllerContext.cpp @@ -138,12 +138,12 @@ int PointerControllerContext::LooperCallback::handleEvent(int /* fd */, int even return 1; // keep the callback } -void PointerControllerContext::addAnimationCallback(int32_t displayId, +void PointerControllerContext::addAnimationCallback(ui::LogicalDisplayId displayId, std::function<bool(nsecs_t)> callback) { mAnimator.addCallback(displayId, callback); } -void PointerControllerContext::removeAnimationCallback(int32_t displayId) { +void PointerControllerContext::removeAnimationCallback(ui::LogicalDisplayId displayId) { mAnimator.removeCallback(displayId); } @@ -161,14 +161,14 @@ void PointerControllerContext::PointerAnimator::initializeDisplayEventReceiver() } } -void PointerControllerContext::PointerAnimator::addCallback(int32_t displayId, +void PointerControllerContext::PointerAnimator::addCallback(ui::LogicalDisplayId displayId, std::function<bool(nsecs_t)> callback) { std::scoped_lock lock(mLock); mLocked.callbacks[displayId] = callback; startAnimationLocked(); } -void PointerControllerContext::PointerAnimator::removeCallback(int32_t displayId) { +void PointerControllerContext::PointerAnimator::removeCallback(ui::LogicalDisplayId displayId) { std::scoped_lock lock(mLock); auto it = mLocked.callbacks.find(displayId); if (it == mLocked.callbacks.end()) { diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h index e893c49d80e5..d42214883d3a 100644 --- a/libs/input/PointerControllerContext.h +++ b/libs/input/PointerControllerContext.h @@ -72,12 +72,13 @@ protected: virtual ~PointerControllerPolicyInterface() {} public: - virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) = 0; - virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) = 0; + virtual void loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId) = 0; + virtual void loadPointerResources(PointerResources* outResources, + ui::LogicalDisplayId displayId) = 0; virtual void loadAdditionalMouseResources( std::map<PointerIconStyle, SpriteIcon>* outResources, std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, - int32_t displayId) = 0; + ui::LogicalDisplayId displayId) = 0; virtual PointerIconStyle getDefaultPointerIconId() = 0; virtual PointerIconStyle getDefaultStylusIconId() = 0; virtual PointerIconStyle getCustomPointerIconId() = 0; @@ -102,7 +103,7 @@ public: nsecs_t getAnimationTime(); - void clearSpotsByDisplay(int32_t displayId); + void clearSpotsByDisplay(ui::LogicalDisplayId displayId); void setHandlerController(std::shared_ptr<PointerController> controller); void setCallbackController(std::shared_ptr<PointerController> controller); @@ -112,8 +113,9 @@ public: void handleDisplayEvents(); - void addAnimationCallback(int32_t displayId, std::function<bool(nsecs_t)> callback); - void removeAnimationCallback(int32_t displayId); + void addAnimationCallback(ui::LogicalDisplayId displayId, + std::function<bool(nsecs_t)> callback); + void removeAnimationCallback(ui::LogicalDisplayId displayId); class MessageHandler : public virtual android::MessageHandler { public: @@ -136,8 +138,8 @@ private: public: PointerAnimator(PointerControllerContext& context); - void addCallback(int32_t displayId, std::function<bool(nsecs_t)> callback); - void removeCallback(int32_t displayId); + void addCallback(ui::LogicalDisplayId displayId, std::function<bool(nsecs_t)> callback); + void removeCallback(ui::LogicalDisplayId displayId); void handleVsyncEvents(); nsecs_t getAnimationTimeLocked(); @@ -148,7 +150,7 @@ private: bool animationPending{false}; nsecs_t animationTime{systemTime(SYSTEM_TIME_MONOTONIC)}; - std::unordered_map<int32_t, std::function<bool(nsecs_t)>> callbacks; + std::unordered_map<ui::LogicalDisplayId, std::function<bool(nsecs_t)>> callbacks; } mLocked GUARDED_BY(mLock); DisplayEventReceiver mDisplayEventReceiver; diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp index 0baa9291f54d..af499390d390 100644 --- a/libs/input/SpriteController.cpp +++ b/libs/input/SpriteController.cpp @@ -19,9 +19,9 @@ #include "SpriteController.h" -#include <log/log.h> -#include <utils/String8.h> +#include <android-base/logging.h> #include <gui/Surface.h> +#include <utils/String8.h> namespace android { @@ -340,13 +340,14 @@ void SpriteController::ensureSurfaceComposerClient() { } } -sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height, int32_t displayId, +sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height, + ui::LogicalDisplayId displayId, bool hideOnMirrored) { ensureSurfaceComposerClient(); const sp<SurfaceControl> parent = mParentSurfaceProvider(displayId); if (parent == nullptr) { - ALOGE("Failed to get the parent surface for pointers on display %d", displayId); + LOG(ERROR) << "Failed to get the parent surface for pointers on display " << displayId; } int32_t createFlags = ISurfaceComposerClient::eHidden | ISurfaceComposerClient::eCursorWindow; @@ -475,7 +476,7 @@ void SpriteController::SpriteImpl::setTransformationMatrix( } } -void SpriteController::SpriteImpl::setDisplayId(int32_t displayId) { +void SpriteController::SpriteImpl::setDisplayId(ui::LogicalDisplayId displayId) { AutoMutex _l(mController.mLock); if (mLocked.state.displayId != displayId) { diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h index fdb15506fd0c..070c90c372ff 100644 --- a/libs/input/SpriteController.h +++ b/libs/input/SpriteController.h @@ -95,7 +95,7 @@ public: virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0; /* Sets the id of the display where the sprite should be shown. */ - virtual void setDisplayId(int32_t displayId) = 0; + virtual void setDisplayId(ui::LogicalDisplayId displayId) = 0; /* Sets the flag to hide sprite on mirrored displays. * This will add ISurfaceComposerClient::eSkipScreenshot flag to the sprite. */ @@ -115,7 +115,7 @@ public: */ class SpriteController { public: - using ParentSurfaceProvider = std::function<sp<SurfaceControl>(int /*displayId*/)>; + using ParentSurfaceProvider = std::function<sp<SurfaceControl>(ui::LogicalDisplayId)>; SpriteController(const sp<Looper>& looper, int32_t overlayLayer, ParentSurfaceProvider parent); SpriteController(const SpriteController&) = delete; SpriteController& operator=(const SpriteController&) = delete; @@ -174,7 +174,7 @@ private: int32_t layer{0}; float alpha{1.0f}; SpriteTransformationMatrix transformationMatrix; - int32_t displayId{ADISPLAY_ID_DEFAULT}; + ui::LogicalDisplayId displayId{ui::ADISPLAY_ID_DEFAULT}; sp<SurfaceControl> surfaceControl; int32_t surfaceWidth{0}; @@ -208,7 +208,7 @@ private: virtual void setLayer(int32_t layer); virtual void setAlpha(float alpha); virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix); - virtual void setDisplayId(int32_t displayId); + virtual void setDisplayId(ui::LogicalDisplayId displayId); virtual void setSkipScreenshot(bool skip); inline const SpriteState& getStateLocked() const { @@ -273,7 +273,7 @@ private: void doDisposeSurfaces(); void ensureSurfaceComposerClient(); - sp<SurfaceControl> obtainSurface(int32_t width, int32_t height, int32_t displayId, + sp<SurfaceControl> obtainSurface(int32_t width, int32_t height, ui::LogicalDisplayId displayId, bool hideOnMirrored); }; diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp index 530d54129791..7462481f8779 100644 --- a/libs/input/TouchSpotController.cpp +++ b/libs/input/TouchSpotController.cpp @@ -40,7 +40,7 @@ namespace android { // --- Spot --- void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float newX, float newY, - int32_t displayId, bool skipScreenshot) { + ui::LogicalDisplayId displayId, bool skipScreenshot) { sprite->setLayer(Sprite::BASE_LAYER_SPOT + id); sprite->setAlpha(alpha); sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale)); @@ -69,7 +69,8 @@ void TouchSpotController::Spot::dump(std::string& out, const char* prefix) const // --- TouchSpotController --- -TouchSpotController::TouchSpotController(int32_t displayId, PointerControllerContext& context) +TouchSpotController::TouchSpotController(ui::LogicalDisplayId displayId, + PointerControllerContext& context) : mDisplayId(displayId), mContext(context) { mContext.getPolicy()->loadPointerResources(&mResources, mDisplayId); } @@ -94,7 +95,7 @@ void TouchSpotController::setSpots(const PointerCoords* spotCoords, const uint32 const PointerCoords& c = spotCoords[spotIdToIndex[id]]; ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f, displayId=%" PRId32 ".", id, c.getAxisValue(AMOTION_EVENT_AXIS_X), c.getAxisValue(AMOTION_EVENT_AXIS_Y), - c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), mDisplayId); + c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), mDisplayId.id); } #endif @@ -274,7 +275,7 @@ void TouchSpotController::dump(std::string& out, const char* prefix) const { out += prefix; out += "SpotController:\n"; out += prefix; - StringAppendF(&out, INDENT "DisplayId: %" PRId32 "\n", mDisplayId); + StringAppendF(&out, INDENT "DisplayId: %s\n", mDisplayId.toString().c_str()); std::scoped_lock lock(mLock); out += prefix; StringAppendF(&out, INDENT "Animating: %s\n", toString(mLocked.animating)); diff --git a/libs/input/TouchSpotController.h b/libs/input/TouchSpotController.h index 608653c6a2e7..ac37fa430249 100644 --- a/libs/input/TouchSpotController.h +++ b/libs/input/TouchSpotController.h @@ -29,7 +29,7 @@ namespace android { */ class TouchSpotController { public: - TouchSpotController(int32_t displayId, PointerControllerContext& context); + TouchSpotController(ui::LogicalDisplayId displayId, PointerControllerContext& context); ~TouchSpotController(); void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits, bool skipScreenshot); @@ -59,7 +59,7 @@ private: y(0.0f), mLastIcon(nullptr) {} - void updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId, + void updateSprite(const SpriteIcon* icon, float x, float y, ui::LogicalDisplayId displayId, bool skipScreenshot); void dump(std::string& out, const char* prefix = "") const; @@ -67,7 +67,7 @@ private: const SpriteIcon* mLastIcon; }; - int32_t mDisplayId; + ui::LogicalDisplayId mDisplayId; mutable std::mutex mLock; diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index 3bc0e24b6e2e..7a133801f514 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -52,12 +52,13 @@ std::pair<float, float> getHotSpotCoordinatesForType(int32_t type) { class MockPointerControllerPolicyInterface : public PointerControllerPolicyInterface { public: - virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId) override; - virtual void loadPointerResources(PointerResources* outResources, int32_t displayId) override; + virtual void loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId) override; + virtual void loadPointerResources(PointerResources* outResources, + ui::LogicalDisplayId displayId) override; virtual void loadAdditionalMouseResources( std::map<PointerIconStyle, SpriteIcon>* outResources, std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, - int32_t displayId) override; + ui::LogicalDisplayId displayId) override; virtual PointerIconStyle getDefaultPointerIconId() override; virtual PointerIconStyle getDefaultStylusIconId() override; virtual PointerIconStyle getCustomPointerIconId() override; @@ -73,13 +74,13 @@ private: bool additionalMouseResourcesLoaded{false}; }; -void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, int32_t) { +void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId) { loadPointerIconForType(icon, CURSOR_TYPE_DEFAULT); pointerIconLoaded = true; } void MockPointerControllerPolicyInterface::loadPointerResources(PointerResources* outResources, - int32_t) { + ui::LogicalDisplayId) { loadPointerIconForType(&outResources->spotHover, CURSOR_TYPE_HOVER); loadPointerIconForType(&outResources->spotTouch, CURSOR_TYPE_TOUCH); loadPointerIconForType(&outResources->spotAnchor, CURSOR_TYPE_ANCHOR); @@ -88,7 +89,7 @@ void MockPointerControllerPolicyInterface::loadPointerResources(PointerResources void MockPointerControllerPolicyInterface::loadAdditionalMouseResources( std::map<PointerIconStyle, SpriteIcon>* outResources, - std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t) { + std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, ui::LogicalDisplayId) { SpriteIcon icon; PointerAnimation anim; @@ -165,7 +166,7 @@ protected: PointerControllerTest(); ~PointerControllerTest(); - void ensureDisplayViewportIsSet(int32_t displayId = ADISPLAY_ID_DEFAULT); + void ensureDisplayViewportIsSet(ui::LogicalDisplayId displayId = ui::ADISPLAY_ID_DEFAULT); sp<MockSprite> mPointerSprite; sp<MockPointerControllerPolicyInterface> mPolicy; @@ -204,7 +205,7 @@ PointerControllerTest::~PointerControllerTest() { mThread.join(); } -void PointerControllerTest::ensureDisplayViewportIsSet(int32_t displayId) { +void PointerControllerTest::ensureDisplayViewportIsSet(ui::LogicalDisplayId displayId) { DisplayViewport viewport; viewport.displayId = displayId; viewport.logicalRight = 1600; @@ -334,23 +335,23 @@ TEST_F(PointerControllerTest, updatesSkipScreenshotFlagForTouchSpots) { // Update spots to sync state with sprite mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits, - ADISPLAY_ID_DEFAULT); + ui::ADISPLAY_ID_DEFAULT); testing::Mock::VerifyAndClearExpectations(testSpotSprite.get()); // Marking the display to skip screenshot should update sprite as well - mPointerController->setSkipScreenshot(ADISPLAY_ID_DEFAULT, true); + mPointerController->setSkipScreenshot(ui::ADISPLAY_ID_DEFAULT, true); EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(true)); // Update spots to sync state with sprite mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits, - ADISPLAY_ID_DEFAULT); + ui::ADISPLAY_ID_DEFAULT); testing::Mock::VerifyAndClearExpectations(testSpotSprite.get()); // Reset flag and verify again - mPointerController->setSkipScreenshot(ADISPLAY_ID_DEFAULT, false); + mPointerController->setSkipScreenshot(ui::ADISPLAY_ID_DEFAULT, false); EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(false)); mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits, - ADISPLAY_ID_DEFAULT); + ui::ADISPLAY_ID_DEFAULT); testing::Mock::VerifyAndClearExpectations(testSpotSprite.get()); } diff --git a/libs/input/tests/mocks/MockSprite.h b/libs/input/tests/mocks/MockSprite.h index 0867221d9eed..21628fb9f72c 100644 --- a/libs/input/tests/mocks/MockSprite.h +++ b/libs/input/tests/mocks/MockSprite.h @@ -33,7 +33,7 @@ public: MOCK_METHOD(void, setLayer, (int32_t), (override)); MOCK_METHOD(void, setAlpha, (float), (override)); MOCK_METHOD(void, setTransformationMatrix, (const SpriteTransformationMatrix&), (override)); - MOCK_METHOD(void, setDisplayId, (int32_t), (override)); + MOCK_METHOD(void, setDisplayId, (ui::LogicalDisplayId), (override)); MOCK_METHOD(void, setSkipScreenshot, (bool), (override)); }; diff --git a/libs/input/tests/mocks/MockSpriteController.h b/libs/input/tests/mocks/MockSpriteController.h index 62f1d65e77a5..9ef6b7c3b480 100644 --- a/libs/input/tests/mocks/MockSpriteController.h +++ b/libs/input/tests/mocks/MockSpriteController.h @@ -27,7 +27,7 @@ class MockSpriteController : public SpriteController { public: MockSpriteController(sp<Looper> looper) - : SpriteController(looper, 0, [](int) { return nullptr; }) {} + : SpriteController(looper, 0, [](ui::LogicalDisplayId) { return nullptr; }) {} ~MockSpriteController() {} MOCK_METHOD(sp<Sprite>, createSprite, (), (override)); diff --git a/nfc/api/current.txt b/nfc/api/current.txt index 6d4cc3a9ca44..cf7aea405756 100644 --- a/nfc/api/current.txt +++ b/nfc/api/current.txt @@ -64,8 +64,10 @@ package android.nfc { } public final class NfcAdapter { + method @FlaggedApi("android.nfc.nfc_state_change") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(); method public void disableForegroundDispatch(android.app.Activity); method public void disableReaderMode(android.app.Activity); + method @FlaggedApi("android.nfc.nfc_state_change") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable(); method public void enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]); method public void enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle); method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context); diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt index 310130e59bfe..a33e2252019b 100644 --- a/nfc/api/system-current.txt +++ b/nfc/api/system-current.txt @@ -3,9 +3,7 @@ package android.nfc { public final class NfcAdapter { method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean addNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler, String[]); - method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable(boolean); - method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable(); method @FlaggedApi("android.nfc.enable_nfc_reader_option") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableReaderOption(boolean); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean); method @FlaggedApi("android.nfc.enable_nfc_mainline") public int getAdapterState(); diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java index e43d10422729..06098deb8aff 100644 --- a/nfc/java/android/nfc/NfcAdapter.java +++ b/nfc/java/android/nfc/NfcAdapter.java @@ -1106,6 +1106,9 @@ public final class NfcAdapter { * {@link #ACTION_ADAPTER_STATE_CHANGED} broadcasts to find out when the * operation is complete. * + * <p>This API is only allowed to be called by system apps + * or apps which are Device Owner or Profile Owner. + * * <p>If this returns true, then either NFC is already on, or * a {@link #ACTION_ADAPTER_STATE_CHANGED} broadcast will be sent * to indicate a state transition. If this returns false, then @@ -1113,9 +1116,8 @@ public final class NfcAdapter { * NFC on (for example we are in airplane mode and NFC is not * toggleable in airplane mode on this platform). * - * @hide */ - @SystemApi + @FlaggedApi(Flags.FLAG_NFC_STATE_CHANGE) @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable() { try { @@ -1146,15 +1148,17 @@ public final class NfcAdapter { * {@link #ACTION_ADAPTER_STATE_CHANGED} broadcasts to find out when the * operation is complete. * + * <p>This API is only allowed to be called by system apps + * or apps which are Device Owner or Profile Owner. + * * <p>If this returns true, then either NFC is already off, or * a {@link #ACTION_ADAPTER_STATE_CHANGED} broadcast will be sent * to indicate a state transition. If this returns false, then * there is some problem that prevents an attempt to turn * NFC off. * - * @hide */ - @SystemApi + @FlaggedApi(Flags.FLAG_NFC_STATE_CHANGE) @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean disable() { try { diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig index 73b29db0f317..cb2a48c2913f 100644 --- a/nfc/java/android/nfc/flags.aconfig +++ b/nfc/java/android/nfc/flags.aconfig @@ -93,3 +93,11 @@ flag { description: "Enable NFC OEM extension support" bug: "331206243" } + +flag { + name: "nfc_state_change" + is_exported: true + namespace: "nfc" + description: "Enable nfc state change API" + bug: "319934052" +} diff --git a/packages/CompanionDeviceManager/res/values-fr/strings.xml b/packages/CompanionDeviceManager/res/values-fr/strings.xml index a33e0dc25d8a..7015c50071a9 100644 --- a/packages/CompanionDeviceManager/res/values-fr/strings.xml +++ b/packages/CompanionDeviceManager/res/values-fr/strings.xml @@ -38,7 +38,7 @@ <string name="helper_title_computer" msgid="4671071173916176037">"Services Google Play"</string> <string name="helper_summary_computer" msgid="8774832742608187072">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DISPLAY_NAME">%2$s</xliff:g> pour accéder aux photos, contenus multimédias et notifications de votre téléphone"</string> <string name="title_nearby_device_streaming" msgid="7269956847378799794">"Autoriser <strong><xliff:g id="DEVICE_NAME">%1$s</xliff:g></strong> à effectuer cette action ?"</string> - <string name="title_nearby_device_streaming_with_mirroring" msgid="242855799919611657">"Autoriser <strong><xliff:g id="DEVICE_NAME">%1$s</xliff:g></strong> à caster les applications et les fonctionnalités système de votre téléphone ?"</string> + <string name="title_nearby_device_streaming_with_mirroring" msgid="242855799919611657">"Autoriser <strong><xliff:g id="DEVICE_NAME">%1$s</xliff:g></strong> à streamer les applications et les fonctionnalités système de votre téléphone ?"</string> <string name="summary_nearby_device_streaming" msgid="4039565463149145573">"%1$s aura accès à tout ce qui est visible ou lu sur le téléphone, y compris les contenus audio, les photos, les informations de paiement, les mots de passe et les messages.<br/><br/>%1$s pourra caster des applications et des fonctionnalités système jusqu\'à ce que vous supprimiez l\'accès à cette autorisation."</string> <string name="helper_summary_nearby_device_streaming" msgid="2063965070936844876">"<xliff:g id="APP_NAME">%1$s</xliff:g> demande l\'autorisation au nom de votre <xliff:g id="DEVICE_NAME">%2$s</xliff:g> de diffuser des applis et d\'autres fonctionnalités système en streaming sur des appareils à proximité"</string> <string name="profile_name_generic" msgid="6851028682723034988">"appareil"</string> diff --git a/packages/CompanionDeviceManager/res/values-hu/strings.xml b/packages/CompanionDeviceManager/res/values-hu/strings.xml index 4985ae3cde34..c929ee4dc991 100644 --- a/packages/CompanionDeviceManager/res/values-hu/strings.xml +++ b/packages/CompanionDeviceManager/res/values-hu/strings.xml @@ -54,7 +54,7 @@ <string name="vendor_header_button_description" msgid="7994879208461111473">"További információ"</string> <string name="permission_phone" msgid="2661081078692784919">"Telefon"</string> <string name="permission_sms" msgid="6337141296535774786">"SMS"</string> - <string name="permission_contacts" msgid="3858319347208004438">"Címtár"</string> + <string name="permission_contacts" msgid="3858319347208004438">"Névjegyek"</string> <string name="permission_calendar" msgid="6805668388691290395">"Naptár"</string> <string name="permission_microphone" msgid="2152206421428732949">"Mikrofon"</string> <string name="permission_call_logs" msgid="5546761417694586041">"Hívásnaplók"</string> diff --git a/packages/CredentialManager/res/values-es-rUS/strings.xml b/packages/CredentialManager/res/values-es-rUS/strings.xml index ef27359946b3..f74990925332 100644 --- a/packages/CredentialManager/res/values-es-rUS/strings.xml +++ b/packages/CredentialManager/res/values-es-rUS/strings.xml @@ -91,7 +91,7 @@ <string name="no_sign_in_info_in" msgid="2641118151920288356">"No hay información de acceso en <xliff:g id="SOURCE">%1$s</xliff:g>"</string> <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Administrar accesos"</string> <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Desde otro dispositivo"</string> - <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar otra voz"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Usar otro dispositivo"</string> <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> canceló la solicitud"</string> <string name="dropdown_presentation_more_sign_in_options_text" msgid="1693727354272417902">"Opciones de acceso"</string> <string name="more_options_content_description" msgid="1323427365788198808">"Más"</string> diff --git a/packages/CredentialManager/res/values-or/strings.xml b/packages/CredentialManager/res/values-or/strings.xml index e3a9191ab225..9885a1f8fa62 100644 --- a/packages/CredentialManager/res/values-or/strings.xml +++ b/packages/CredentialManager/res/values-or/strings.xml @@ -57,9 +57,9 @@ <string name="set_as_default" msgid="4415328591568654603">"ଡିଫଲ୍ଟ ଭାବେ ସେଟ କରନ୍ତୁ"</string> <string name="settings" msgid="6536394145760913145">"ସେଟିଂସ"</string> <string name="use_once" msgid="9027366575315399714">"ଥରେ ବ୍ୟବହାର କରନ୍ତୁ"</string> - <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>ଟି ପାସୱାର୍ଡ • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>ଟି ପାସକୀ"</string> - <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>ଟି ପାସୱାର୍ଡ"</string> - <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>ଟି ପାସକୀ"</string> + <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ପାସୱାର୍ଡ • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ପାସକୀ"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ପାସୱାର୍ଡ"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> ପାସକୀ"</string> <string name="more_options_usage_credentials" msgid="1785697001787193984">"<xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g>ଟି କ୍ରେଡେନସିଆଲ"</string> <string name="passkey_before_subtitle" msgid="2448119456208647444">"ପାସକୀ"</string> <string name="another_device" msgid="5147276802037801217">"ଅନ୍ୟ ଏକ ଡିଭାଇସ"</string> diff --git a/packages/CredentialManager/res/values-zh-rHK/strings.xml b/packages/CredentialManager/res/values-zh-rHK/strings.xml index 44f5eaa543b3..c6ac74358be4 100644 --- a/packages/CredentialManager/res/values-zh-rHK/strings.xml +++ b/packages/CredentialManager/res/values-zh-rHK/strings.xml @@ -74,7 +74,7 @@ <string name="get_dialog_description_single_tap" msgid="2797059565126030879">"使用螢幕鎖定方式以 <xliff:g id="USERNAME">%2$s</xliff:g> 登入 <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="get_dialog_title_unlock_options_for" msgid="7096423827682163270">"解鎖 <xliff:g id="APP_NAME">%1$s</xliff:g> 的登入選項"</string> <string name="get_dialog_title_choose_passkey_for" msgid="9175997688078538490">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」密鑰"</string> - <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」密碼"</string> + <string name="get_dialog_title_choose_password_for" msgid="1724435823820819221">"選擇「<xliff:g id="APP_NAME">%1$s</xliff:g>」的儲存密碼"</string> <string name="get_dialog_title_choose_saved_sign_in_for" msgid="2420298653461652728">"選擇已儲存的「<xliff:g id="APP_NAME">%1$s</xliff:g>」登入資料"</string> <string name="get_dialog_title_choose_sign_in_for" msgid="645728947702442421">"選擇使用 <xliff:g id="APP_NAME">%1$s</xliff:g> 的帳戶"</string> <string name="get_dialog_title_choose_option_for" msgid="4976380044745029107">"要選擇適用於「<xliff:g id="APP_NAME">%1$s</xliff:g>」的項目嗎?"</string> diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt index 50ebdd5e3ce7..0da32bddd928 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt @@ -18,6 +18,7 @@ package com.android.credentialmanager.autofill import android.app.PendingIntent import android.app.assist.AssistStructure +import android.content.ComponentName import android.content.Context import android.credentials.CredentialManager import android.credentials.GetCredentialRequest @@ -34,6 +35,10 @@ import android.os.CancellationSignal import android.os.OutcomeReceiver import android.os.ResultReceiver import android.service.autofill.AutofillService +import com.android.credentialmanager.model.get.ProviderInfo +import androidx.core.graphics.drawable.toBitmap +import com.android.credentialmanager.model.get.ActionEntryInfo +import com.android.credentialmanager.model.EntryInfo import android.service.autofill.Dataset import android.service.autofill.Field import android.service.autofill.FillCallback @@ -63,7 +68,8 @@ import com.android.credentialmanager.getflow.ProviderDisplayInfo import com.android.credentialmanager.getflow.toProviderDisplayInfo import com.android.credentialmanager.ktx.credentialEntry import com.android.credentialmanager.model.CredentialType -import com.android.credentialmanager.model.get.CredentialEntryInfo +import java.util.ArrayList +import java.util.Objects import java.util.concurrent.Executors import org.json.JSONException import org.json.JSONObject @@ -121,8 +127,11 @@ class CredentialAutofillService : AutofillService() { val responseClientState = Bundle() responseClientState.putBoolean(WEBVIEW_REQUESTED_CREDENTIAL_KEY, false) - val getCredRequest: GetCredentialRequest? = getCredManRequest(structure, sessionId, - requestId, resultReceiver, responseClientState) + val uniqueAutofillIdsForRequest: MutableSet<AutofillId> = mutableSetOf() + val getCredRequest: GetCredentialRequest? = getCredManRequest( + structure, sessionId, + requestId, resultReceiver, responseClientState, uniqueAutofillIdsForRequest + ) // TODO(b/324635774): Use callback for validating. If the request is coming // directly from the view, there should be a corresponding callback, otherwise // we should fail fast, @@ -132,14 +141,17 @@ class CredentialAutofillService : AutofillService() { return } val credentialManager: CredentialManager = - getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager + getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager val outcome = object : OutcomeReceiver<GetCandidateCredentialsResponse, GetCandidateCredentialsException> { override fun onResult(result: GetCandidateCredentialsResponse) { Log.i(TAG, "getCandidateCredentials onResult") - val fillResponse = convertToFillResponse(result, request, - responseClientState, GetFlowUtils.extractTypePriorityMap(getCredRequest)) + val fillResponse = convertToFillResponse( + result, request, + responseClientState, GetFlowUtils.extractTypePriorityMap(getCredRequest), + uniqueAutofillIdsForRequest + ) if (fillResponse != null) { callback.onSuccess(fillResponse) } else { @@ -195,58 +207,131 @@ class CredentialAutofillService : AutofillService() { private fun convertToFillResponse( getCredResponse: GetCandidateCredentialsResponse, - filLRequest: FillRequest, + fillRequest: FillRequest, responseClientState: Bundle, typePriorityMap: Map<String, Int>, + uniqueAutofillIdsForRequest: MutableSet<AutofillId> ): FillResponse? { val candidateProviders = getCredResponse.candidateProviderDataList if (candidateProviders.isEmpty()) { return null } - + val primaryProviderComponentName = getCredResponse.primaryProviderComponentName val entryIconMap: Map<String, Icon> = getEntryToIconMap(candidateProviders) val autofillIdToProvidersMap: Map<AutofillId, ArrayList<GetCredentialProviderData>> = - mapAutofillIdToProviders(candidateProviders) + mapAutofillIdToProviders( + uniqueAutofillIdsForRequest, + candidateProviders, + primaryProviderComponentName + ) val fillResponseBuilder = FillResponse.Builder() fillResponseBuilder.setFlags(FillResponse.FLAG_CREDENTIAL_MANAGER_RESPONSE) - var validFillResponse = false autofillIdToProvidersMap.forEach { (autofillId, providers) -> - validFillResponse = processProvidersForAutofillId( - filLRequest, autofillId, providers, entryIconMap, fillResponseBuilder, - getCredResponse.intent, typePriorityMap) - .or(validFillResponse) + var credentialDatasetAdded = addCredentialDatasetsForAutofillId(fillRequest, + autofillId, providers, entryIconMap, fillResponseBuilder, typePriorityMap) + if (!credentialDatasetAdded && primaryProviderComponentName != null) { + val providerList = GetFlowUtils.toProviderList( + providers, + this@CredentialAutofillService + ) + val primaryProviderInfo = + providerList.find { provider -> primaryProviderComponentName + .flattenToString().equals(provider.id) } + if (primaryProviderInfo != null) { + addActionDatasetsForAutofillId( + fillRequest, + autofillId, + primaryProviderInfo, + fillResponseBuilder + ) + } + } } - if (!validFillResponse) { - return null + for (autofillId in uniqueAutofillIdsForRequest) { + addMoreOptionsDataset( + fillRequest, + autofillId, + fillResponseBuilder, + getCredResponse.intent.putExtra( + ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, ArrayList(candidateProviders) + ) + ) } fillResponseBuilder.setClientState(responseClientState) return fillResponseBuilder.build() } - private fun processProvidersForAutofillId( - filLRequest: FillRequest, - autofillId: AutofillId, - providerDataList: ArrayList<GetCredentialProviderData>, - entryIconMap: Map<String, Icon>, - fillResponseBuilder: FillResponse.Builder, - bottomSheetIntent: Intent, - typePriorityMap: Map<String, Int>, + private fun addActionDatasetsForAutofillId( + fillRequest: FillRequest, + autofillId: AutofillId, + primaryProvider: ProviderInfo, + fillResponseBuilder: FillResponse.Builder, + ): Boolean { + var index = 0 + var datasetAdded = false + primaryProvider.actionEntryList.forEach { actionEntry -> + if (index >= maxDatasetDisplayLimit(primaryProvider.actionEntryList.size)) { + return@forEach + } + val pendingIntent = actionEntry.pendingIntent + if (pendingIntent == null) { + Log.e(TAG, "Pending intent for action chip is null") + return@forEach + } + + val icon: Icon? = Icon.createWithBitmap(actionEntry.icon.toBitmap()) + if (icon == null) { + Log.e(TAG, "Icon for action chip is null") + return@forEach + } + + val presentations = constructPresentations( + fillRequest, + index, + actionEntry, + pendingIntent, + icon, + actionEntry.title, + actionEntry.subTitle, + primaryProvider.actionEntryList.size + ) + + fillResponseBuilder.addDataset( + Dataset.Builder() + .setField( + autofillId, + Field.Builder().setPresentations(presentations).build() + ) + .setAuthentication(pendingIntent.intentSender) + .build() + ) + datasetAdded = true + + index++ + } + + return datasetAdded + } + + private fun addCredentialDatasetsForAutofillId( + fillRequest: FillRequest, + autofillId: AutofillId, + providerDataList: List<GetCredentialProviderData>, + entryIconMap: Map<String, Icon>, + fillResponseBuilder: FillResponse.Builder, + typePriorityMap: Map<String, Int>, ): Boolean { val providerList = GetFlowUtils.toProviderList( providerDataList, - this@CredentialAutofillService) + this@CredentialAutofillService + ) if (providerList.isEmpty()) { return false } val providerDisplayInfo: ProviderDisplayInfo = - toProviderDisplayInfo(providerList, typePriorityMap) + toProviderDisplayInfo(providerList, typePriorityMap) var totalEntryCount = providerDisplayInfo.sortedUserNameToCredentialEntryList.size - val inlineSuggestionsRequest = filLRequest.inlineSuggestionsRequest - val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs - val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0 - val maxDatasetDisplayLimit = this.resources.getInteger( - com.android.credentialmanager.R.integer.autofill_max_visible_datasets) - .coerceAtMost(totalEntryCount) + var i = 0 var datasetAdded = false @@ -260,8 +345,6 @@ class CredentialAutofillService : AutofillService() { } } } - bottomSheetIntent.putExtra( - ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, providerDataList) providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@{ val primaryEntry = it.sortedCredentialEntryList.first() val pendingIntent = primaryEntry.pendingIntent @@ -271,7 +354,7 @@ class CredentialAutofillService : AutofillService() { Log.e(TAG, "PendingIntent was missing from the entry.") return@usernameLoop } - if (i >= maxDatasetDisplayLimit) { + if (i >= maxDatasetDisplayLimit(totalEntryCount)) { return@usernameLoop } val icon: Icon = if (primaryEntry.icon == null) { @@ -280,116 +363,172 @@ class CredentialAutofillService : AutofillService() { getDefaultIcon() } else { entryIconMap[primaryEntry.entryKey + primaryEntry.entrySubkey] - ?: getDefaultIcon() - } - // Create inline presentation - var inlinePresentation: InlinePresentation? = null - if (inlinePresentationSpecs != null && i < maxDatasetDisplayLimit) { - val spec: InlinePresentationSpec? = if (i < inlinePresentationSpecsCount) { - inlinePresentationSpecs[i] - } else { - inlinePresentationSpecs[inlinePresentationSpecsCount - 1] - } - if (spec != null) { - inlinePresentation = createInlinePresentation(primaryEntry, pendingIntent, icon, - InlinePresentationsFactory.modifyInlinePresentationSpec - (this@CredentialAutofillService, spec), - duplicateDisplayNamesForPasskeys) - } - } - var dropdownPresentation: RemoteViews? = null - if (i < maxDatasetDisplayLimit) { - dropdownPresentation = RemoteViewsFactory.createDropdownPresentation( - this, icon, primaryEntry, /*isFirstEntry= */ i == 0, - /*isLastEntry= */ (totalEntryCount - i == 1)) + ?: getDefaultIcon() } - - val dataSetBuilder = Dataset.Builder() - val presentationBuilder = Presentations.Builder() - if (dropdownPresentation != null) { - presentationBuilder.setMenuPresentation(dropdownPresentation) + val displayName = primaryEntry.displayName + val title: String = if (primaryEntry.credentialType == CredentialType.PASSKEY && + displayName != null + ) { + displayName + } else { + primaryEntry.userName } - if (inlinePresentation != null) { - presentationBuilder.setInlinePresentation(inlinePresentation) + val subtitle = if (primaryEntry.credentialType == + CredentialType.PASSKEY && duplicateDisplayNamesForPasskeys[title] == true + ) { + primaryEntry.userName + } else { + null } - + val presentations = + constructPresentations( + fillRequest, i, primaryEntry, pendingIntent, + icon, title, subtitle, totalEntryCount + ) fillResponseBuilder.addDataset( - dataSetBuilder - .setField( - autofillId, - Field.Builder().setPresentations( - presentationBuilder.build()) - .build()) - .setAuthentication(pendingIntent.intentSender) - .setCredentialFillInIntent(fillInIntent) - .build()) + Dataset.Builder() + .setField( + autofillId, + Field.Builder().setPresentations( + presentations + ) + .build() + ) + .setAuthentication(pendingIntent.intentSender) + .setCredentialFillInIntent(fillInIntent) + .build() + ) datasetAdded = true i++ } - val pinnedSpec = getLastInlinePresentationSpec(inlinePresentationSpecs, - inlinePresentationSpecsCount) - if (datasetAdded) { - addDropdownMoreOptionsPresentation(bottomSheetIntent, autofillId, fillResponseBuilder) - if (pinnedSpec != null) { - addPinnedInlineSuggestion(pinnedSpec, autofillId, - fillResponseBuilder, bottomSheetIntent) + return datasetAdded + } + + private fun addMoreOptionsDataset( + fillRequest: FillRequest, + autofillId: AutofillId, + fillResponseBuilder: FillResponse.Builder, + bottomSheetIntent: Intent + ) { + val inlineSuggestionsRequest = fillRequest.inlineSuggestionsRequest + val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs + val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0 + val pinnedSpec = getLastInlinePresentationSpec( + inlinePresentationSpecs, + inlinePresentationSpecsCount + ) + addDropdownMoreOptionsPresentation(bottomSheetIntent, autofillId, fillResponseBuilder) + if (pinnedSpec != null) { + addPinnedInlineSuggestion( + pinnedSpec, autofillId, + fillResponseBuilder, bottomSheetIntent + ) + } + } + + private fun constructPresentations( + fillRequest: FillRequest, + index: Int, + entry: EntryInfo, + pendingIntent: PendingIntent, + icon: Icon, + title: String, + subtitle: String?, + totalEntryCount: Int + ): Presentations { + val inlineSuggestionsRequest = fillRequest.inlineSuggestionsRequest + val inlinePresentationSpecs = inlineSuggestionsRequest?.inlinePresentationSpecs + val inlinePresentationSpecsCount = inlinePresentationSpecs?.size ?: 0 + + // Create inline presentation + var inlinePresentation: InlinePresentation? = null + if (inlinePresentationSpecs != null && index < maxDatasetDisplayLimit(totalEntryCount)) { + val spec: InlinePresentationSpec? = if (index < inlinePresentationSpecsCount) { + inlinePresentationSpecs[index] + } else { + inlinePresentationSpecs[inlinePresentationSpecsCount - 1] + } + if (spec != null) { + inlinePresentation = createInlinePresentation( + pendingIntent, icon, + InlinePresentationsFactory.modifyInlinePresentationSpec + (this@CredentialAutofillService, spec), + title, subtitle, entry is ActionEntryInfo + ) } } - return datasetAdded + var dropdownPresentation: RemoteViews? = null + if (index < maxDatasetDisplayLimit(totalEntryCount)) { + dropdownPresentation = RemoteViewsFactory.createDropdownPresentation( + this, icon, entry, /*isFirstEntry= */ index == 0, + /*isLastEntry= */ (totalEntryCount - index == 1) + ) + } + + val presentationBuilder = Presentations.Builder() + if (dropdownPresentation != null) { + presentationBuilder.setMenuPresentation(dropdownPresentation) + } + if (inlinePresentation != null) { + presentationBuilder.setInlinePresentation(inlinePresentation) + } + return presentationBuilder.build() } + private fun maxDatasetDisplayLimit(totalEntryCount: Int) = this.resources.getInteger( + com.android.credentialmanager.R.integer.autofill_max_visible_datasets + ).coerceAtMost(totalEntryCount) + private fun createInlinePresentation( - primaryEntry: CredentialEntryInfo, - pendingIntent: PendingIntent, - icon: Icon, - spec: InlinePresentationSpec, - duplicateDisplayNameForPasskeys: MutableMap<String, Boolean> + pendingIntent: PendingIntent, + icon: Icon, + spec: InlinePresentationSpec, + title: String, + subtitle: String?, + isActionEntry: Boolean ): InlinePresentation { - val displayName: String = if (primaryEntry.credentialType == CredentialType.PASSKEY && - primaryEntry.displayName != null) { - primaryEntry.displayName!! - } else { - primaryEntry.userName - } val sliceBuilder = InlineSuggestionUi - .newContentBuilder(pendingIntent) - .setTitle(displayName) + .newContentBuilder(pendingIntent) + .setTitle(title) icon.setTintBlendMode(BlendMode.DST) sliceBuilder.setStartIcon(icon) - if (primaryEntry.credentialType == - CredentialType.PASSKEY && duplicateDisplayNameForPasskeys[displayName] == true) { - sliceBuilder.setSubtitle(primaryEntry.userName) + if (subtitle != null && !isActionEntry) { + sliceBuilder.setSubtitle(subtitle) } return InlinePresentation( - sliceBuilder.build().slice, spec, /* pinned= */ false) + sliceBuilder.build().slice, spec, /* pinned= */ false + ) } private fun addDropdownMoreOptionsPresentation( - bottomSheetIntent: Intent, - autofillId: AutofillId, - fillResponseBuilder: FillResponse.Builder + bottomSheetIntent: Intent, + autofillId: AutofillId, + fillResponseBuilder: FillResponse.Builder ) { val presentationBuilder = Presentations.Builder() - .setMenuPresentation( - RemoteViewsFactory.createMoreSignInOptionsPresentation(this)) + .setMenuPresentation( + RemoteViewsFactory.createMoreSignInOptionsPresentation(this) + ) val pendingIntent = setUpBottomSheetPendingIntent(bottomSheetIntent) fillResponseBuilder.addDataset( - Dataset.Builder() - .setId(AutofillManager.PINNED_DATASET_ID) - .setField( - autofillId, - Field.Builder().setPresentations( - presentationBuilder.build()) - .build()) - .setAuthentication(pendingIntent.intentSender) + Dataset.Builder() + .setId(AutofillManager.PINNED_DATASET_ID) + .setField( + autofillId, + Field.Builder().setPresentations( + presentationBuilder.build() + ) .build() + ) + .setAuthentication(pendingIntent.intentSender) + .build() ) } private fun getLastInlinePresentationSpec( - inlinePresentationSpecs: List<InlinePresentationSpec>?, - inlinePresentationSpecsCount: Int + inlinePresentationSpecs: List<InlinePresentationSpec>?, + inlinePresentationSpecsCount: Int ): InlinePresentationSpec? { if (inlinePresentationSpecs != null) { return inlinePresentationSpecs[inlinePresentationSpecsCount - 1] @@ -398,40 +537,47 @@ class CredentialAutofillService : AutofillService() { } private fun addPinnedInlineSuggestion( - spec: InlinePresentationSpec, - autofillId: AutofillId, - fillResponseBuilder: FillResponse.Builder, - bottomSheetIntent: Intent + spec: InlinePresentationSpec, + autofillId: AutofillId, + fillResponseBuilder: FillResponse.Builder, + bottomSheetIntent: Intent ) { val pendingIntent = setUpBottomSheetPendingIntent(bottomSheetIntent) val dataSetBuilder = Dataset.Builder() val sliceBuilder = InlineSuggestionUi - .newContentBuilder(pendingIntent) - .setStartIcon(Icon.createWithResource(this, - com.android.credentialmanager.R.drawable.more_horiz_24px)) + .newContentBuilder(pendingIntent) + .setStartIcon( + Icon.createWithResource( + this, + com.android.credentialmanager.R.drawable.more_horiz_24px + ) + ) val presentationBuilder = Presentations.Builder() - .setInlinePresentation(InlinePresentation( - sliceBuilder.build().slice, spec, /* pinned= */ true)) + .setInlinePresentation( + InlinePresentation( + sliceBuilder.build().slice, spec, /* pinned= */ true + ) + ) fillResponseBuilder.addDataset( - dataSetBuilder - .setId(AutofillManager.PINNED_DATASET_ID) - .setField( - autofillId, - Field.Builder().setPresentations( - presentationBuilder.build() - ).build() - ) - .setAuthentication(pendingIntent.intentSender) - .build() + dataSetBuilder + .setId(AutofillManager.PINNED_DATASET_ID) + .setField( + autofillId, + Field.Builder().setPresentations( + presentationBuilder.build() + ).build() + ) + .setAuthentication(pendingIntent.intentSender) + .build() ) } private fun setUpBottomSheetPendingIntent(intent: Intent): PendingIntent { intent.setAction(java.util.UUID.randomUUID().toString()) return PendingIntent.getActivity(this, /*requestCode=*/0, intent, - PendingIntent.FLAG_MUTABLE, /*options=*/null) + PendingIntent.FLAG_MUTABLE, /*options=*/null) } /** @@ -465,17 +611,33 @@ class CredentialAutofillService : AutofillService() { * } */ private fun mapAutofillIdToProviders( - providerList: List<GetCredentialProviderData> + uniqueAutofillIdsForRequest: Set<AutofillId>, + providerList: List<GetCredentialProviderData>, + primaryProviderComponentName: ComponentName? ): Map<AutofillId, ArrayList<GetCredentialProviderData>> { val autofillIdToProviders: MutableMap<AutofillId, ArrayList<GetCredentialProviderData>> = mutableMapOf() + var primaryProvider: GetCredentialProviderData? = null providerList.forEach { provider -> + if (primaryProviderComponentName != null && Objects.equals(ComponentName + .unflattenFromString(provider + .providerFlattenedComponentName), primaryProviderComponentName)) { + primaryProvider = provider + } val autofillIdToCredentialEntries: MutableMap<AutofillId, ArrayList<Entry>> = mapAutofillIdToCredentialEntries(provider.credentialEntries) autofillIdToCredentialEntries.forEach { (autofillId, entries) -> autofillIdToProviders.getOrPut(autofillId) { ArrayList() } - .add(copyProviderInfo(provider, entries)) + .add(copyProviderInfo(provider, entries)) + } + } + // adds primary provider action entries for autofill IDs without credential entries + uniqueAutofillIdsForRequest.forEach { autofillId -> + if (!autofillIdToProviders.containsKey(autofillId) && primaryProvider != null) { + autofillIdToProviders.put( + autofillId, + ArrayList(listOf(copyProviderInfoForActionsOnly(primaryProvider!!)))) } } return autofillIdToProviders @@ -526,19 +688,35 @@ class CredentialAutofillService : AutofillService() { ) } + private fun copyProviderInfoForActionsOnly( + providerInfo: GetCredentialProviderData, + ): GetCredentialProviderData { + return GetCredentialProviderData( + providerInfo.providerFlattenedComponentName, + emptyList(), + providerInfo.actionChips, + emptyList(), + null + ) + } + override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) { TODO("Not yet implemented") } private fun getCredManRequest( - structure: AssistStructure, - sessionId: Int, - requestId: Int, - resultReceiver: ResultReceiver, - responseClientState: Bundle + structure: AssistStructure, + sessionId: Int, + requestId: Int, + resultReceiver: ResultReceiver, + responseClientState: Bundle, + uniqueAutofillIdsForRequest: MutableSet<AutofillId> ): GetCredentialRequest? { val credentialOptions: MutableList<CredentialOption> = mutableListOf() - traverseStructureForRequest(structure, credentialOptions, responseClientState, sessionId) + traverseStructureForRequest( + structure, credentialOptions, responseClientState, + sessionId, uniqueAutofillIdsForRequest + ) if (credentialOptions.isNotEmpty()) { val dataBundle = Bundle() @@ -558,7 +736,8 @@ class CredentialAutofillService : AutofillService() { structure: AssistStructure, cmRequests: MutableList<CredentialOption>, responseClientState: Bundle, - sessionId: Int + sessionId: Int, + uniqueAutofillIdsForRequest: MutableSet<AutofillId> ) { val traversedViewNodes: MutableSet<AutofillId> = mutableSetOf() val credentialOptionsFromHints: MutableMap<String, CredentialOption> = mutableMapOf() @@ -570,7 +749,7 @@ class CredentialAutofillService : AutofillService() { windowNodes.forEach { windowNode: AssistStructure.WindowNode -> traverseNodeForRequest( windowNode.rootViewNode, cmRequests, responseClientState, traversedViewNodes, - credentialOptionsFromHints, sessionId) + credentialOptionsFromHints, sessionId, uniqueAutofillIdsForRequest) } } @@ -580,7 +759,8 @@ class CredentialAutofillService : AutofillService() { responseClientState: Bundle, traversedViewNodes: MutableSet<AutofillId>, credentialOptionsFromHints: MutableMap<String, CredentialOption>, - sessionId: Int + sessionId: Int, + uniqueAutofillIdsForRequest: MutableSet<AutofillId> ) { viewNode.autofillId?.let { val domain = viewNode.webDomain @@ -590,7 +770,9 @@ class CredentialAutofillService : AutofillService() { WEBVIEW_REQUESTED_CREDENTIAL_KEY, true) } cmRequests.addAll(getCredentialOptionsFromViewNode(viewNode, it, responseClientState, - traversedViewNodes, credentialOptionsFromHints, sessionId)) + traversedViewNodes, credentialOptionsFromHints, sessionId, + uniqueAutofillIdsForRequest) + ) traversedViewNodes.add(it) } @@ -600,8 +782,10 @@ class CredentialAutofillService : AutofillService() { } children.forEach { childNode: AssistStructure.ViewNode -> - traverseNodeForRequest(childNode, cmRequests, responseClientState, traversedViewNodes, - credentialOptionsFromHints, sessionId) + traverseNodeForRequest( + childNode, cmRequests, responseClientState, traversedViewNodes, + credentialOptionsFromHints, sessionId, uniqueAutofillIdsForRequest + ) } } @@ -611,7 +795,8 @@ class CredentialAutofillService : AutofillService() { responseClientState: Bundle, traversedViewNodes: MutableSet<AutofillId>, credentialOptionsFromHints: MutableMap<String, CredentialOption>, - sessionId: Int + sessionId: Int, + uniqueAutofillIdsForRequest: MutableSet<AutofillId> ): MutableList<CredentialOption> { val credentialOptions: MutableList<CredentialOption> = mutableListOf() if (Flags.autofillCredmanDevIntegration() && viewNode.pendingCredentialRequest != null) { @@ -641,8 +826,9 @@ class CredentialAutofillService : AutofillService() { CredentialProviderService.EXTRA_AUTOFILL_ID, associatedAutofillIds ) + uniqueAutofillIdsForRequest.addAll(associatedAutofillIds) } - } + } } // TODO(b/325502552): clean up cred option logic in autofill hint val credentialHints: MutableList<String> = mutableListOf() diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt index 7bb08d2c26e8..98e1690ace92 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/RemoteViewsFactory.kt @@ -21,8 +21,9 @@ import android.util.Log import com.android.credentialmanager.common.Constants import android.widget.RemoteViews import androidx.core.content.ContextCompat +import com.android.credentialmanager.model.get.ActionEntryInfo import com.android.credentialmanager.model.get.CredentialEntryInfo -import com.android.credentialmanager.model.CredentialType +import com.android.credentialmanager.model.EntryInfo import android.graphics.drawable.Icon class RemoteViewsFactory { @@ -39,37 +40,47 @@ class RemoteViewsFactory { fun createDropdownPresentation( context: Context, icon: Icon, - credentialEntryInfo: CredentialEntryInfo, + entryInfo: EntryInfo, isFirstEntry: Boolean, isLastEntry: Boolean, ): RemoteViews { var layoutId: Int = com.android.credentialmanager.R.layout .credman_dropdown_presentation_layout val remoteViews = RemoteViews(context.packageName, layoutId) - val displayName = credentialEntryInfo.displayName ?: credentialEntryInfo.userName - remoteViews.setTextViewText(android.R.id.text1, displayName) - val secondaryText = getSecondaryText(credentialEntryInfo) - if (secondaryText.isNullOrBlank()) { - Log.w(Constants.LOG_TAG, "Secondary text for dropdown is null") - } else { - remoteViews.setTextViewText(android.R.id.text2, secondaryText) + if (entryInfo is CredentialEntryInfo) { + val displayName = entryInfo.displayName ?: entryInfo.userName + remoteViews.setTextViewText(android.R.id.text1, displayName) + val secondaryText = getSecondaryText(entryInfo) + if (secondaryText.isNullOrBlank()) { + Log.w(Constants.LOG_TAG, "Secondary text for dropdown credential entry is null") + } else { + remoteViews.setTextViewText(android.R.id.text2, secondaryText) + } + remoteViews.setContentDescription( + android.R.id.icon1, entryInfo + .providerDisplayName + ) + } else if (entryInfo is ActionEntryInfo) { + remoteViews.setTextViewText(android.R.id.text1, entryInfo.title) + remoteViews.setTextViewText(android.R.id.text2, entryInfo.subTitle) } - remoteViews.setImageViewIcon(android.R.id.icon1, icon); + remoteViews.setImageViewIcon(android.R.id.icon1, icon) remoteViews.setBoolean( - android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true); + android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true + ) remoteViews.setInt( android.R.id.icon1, SET_MAX_HEIGHT_METHOD_NAME, context.resources.getDimensionPixelSize( - com.android.credentialmanager.R.dimen.autofill_icon_size)); - remoteViews.setContentDescription(android.R.id.icon1, credentialEntryInfo - .providerDisplayName); + com.android.credentialmanager.R.dimen.autofill_icon_size + ) + ) val drawableId = if (isFirstEntry) com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_one else com.android.credentialmanager.R.drawable.fill_dialog_dynamic_list_item_middle remoteViews.setInt( - android.R.id.content, SET_BACKGROUND_RESOURCE_METHOD_NAME, drawableId); + android.R.id.content, SET_BACKGROUND_RESOURCE_METHOD_NAME, drawableId) if (isFirstEntry) remoteViews.setViewPadding( com.android.credentialmanager.R.id.credential_card, /* left=*/0, @@ -94,8 +105,8 @@ class RemoteViewsFactory { * providerDisplayName. Both credential type and provider display name should not be empty. */ private fun getSecondaryText(credentialEntryInfo: CredentialEntryInfo): String? { - return listOf(if (credentialEntryInfo.displayName != null - && (credentialEntryInfo.displayName != credentialEntryInfo.userName)) + return listOf(if (credentialEntryInfo.displayName != null && + (credentialEntryInfo.displayName != credentialEntryInfo.userName)) (credentialEntryInfo.userName) else null, credentialEntryInfo.credentialTypeDisplayName, credentialEntryInfo.providerDisplayName).filterNot { it.isNullOrBlank() } @@ -113,16 +124,16 @@ class RemoteViewsFactory { com.android.credentialmanager .R.string.dropdown_presentation_more_sign_in_options_text)) remoteViews.setBoolean( - android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true); + android.R.id.icon1, SET_ADJUST_VIEW_BOUNDS_METHOD_NAME, true) remoteViews.setInt( android.R.id.icon1, SET_MAX_HEIGHT_METHOD_NAME, context.resources.getDimensionPixelSize( - com.android.credentialmanager.R.dimen.autofill_icon_size)); + com.android.credentialmanager.R.dimen.autofill_icon_size)) val drawableId = com.android.credentialmanager.R.drawable.more_options_list_item remoteViews.setInt( - android.R.id.content, SET_BACKGROUND_RESOURCE_METHOD_NAME, drawableId); + android.R.id.content, SET_BACKGROUND_RESOURCE_METHOD_NAME, drawableId) return remoteViews } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt index 8e7886119a34..19f5a99f46fa 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt @@ -292,12 +292,12 @@ private fun toGetScreenState( providerDisplayInfo.remoteEntry == null && providerDisplayInfo.authenticationEntryList.all { it.isUnlockedAndEmpty }) GetScreenState.UNLOCKED_AUTH_ENTRIES_ONLY + else if (isRequestForAllOptions) + GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY else if (providerDisplayInfo.sortedUserNameToCredentialEntryList.isEmpty() && providerDisplayInfo.authenticationEntryList.isEmpty() && providerDisplayInfo.remoteEntry != null) GetScreenState.REMOTE_ONLY - else if (isRequestForAllOptions) - GetScreenState.ALL_SIGN_IN_OPTIONS_ONLY else if (isBiometricFlow(providerDisplayInfo, isFlowAutoSelectable(providerDisplayInfo))) GetScreenState.BIOMETRIC_SELECTION else GetScreenState.PRIMARY_SELECTION diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java index 25ac3c9d9074..635dc420f18c 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java @@ -172,7 +172,7 @@ public class DynamicSystemInstallationService extends Service // This is for testing only now private boolean mEnableWhenCompleted; - private boolean mOneShot; + private boolean mOneShot = true; private boolean mHideNotification; private InstallationAsyncTask.Progress mInstallTaskProgress; diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt index d969d1cf0a80..2e9b7b421a7b 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt @@ -446,6 +446,9 @@ class InstallRepository(private val context: Context) { // Returns InstallUserActionRequired stage if install details could be successfully // computed, else it returns InstallAborted. val confirmationSnippet: InstallStage = generateConfirmationSnippet() + if (confirmationSnippet.stageCode == InstallStage.STAGE_ABORTED) { + return confirmationSnippet + } val existingUpdateOwner: CharSequence? = getExistingUpdateOwner(newPackageInfo!!) return if (sessionId == SessionInfo.INVALID_ID && diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml index 7c76ea1ad3b7..221e8f51db89 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml @@ -38,7 +38,7 @@ <color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color> <!-- Dialog background color. --> - <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceInverse</color> + <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainer</color> <color name="settingslib_colorSurfaceHeader">@color/settingslib_materialColorSurfaceVariant</color> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml index 2a6499aaab37..dc2d3dc696cb 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml @@ -38,7 +38,7 @@ <color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color> <!-- Dialog background color. --> - <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceInverse</color> + <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainer</color> <!-- Material next track outline color--> <color name="settingslib_track_online_color">@color/settingslib_switch_track_outline_color</color> diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts index 4147813bd059..e7823df7ce0e 100644 --- a/packages/SettingsLib/Spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/build.gradle.kts @@ -29,7 +29,7 @@ val androidTop: String = File(rootDir, "../../../../..").canonicalPath allprojects { extra["androidTop"] = androidTop - extra["jetpackComposeVersion"] = "1.7.0-alpha05" + extra["jetpackComposeVersion"] = "1.7.0-alpha08" } subprojects { diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts index 6344501ce789..4aa57b3876fc 100644 --- a/packages/SettingsLib/Spa/spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/spa/build.gradle.kts @@ -53,17 +53,17 @@ android { dependencies { api(project(":SettingsLibColor")) - api("androidx.appcompat:appcompat:1.7.0-alpha03") + api("androidx.appcompat:appcompat:1.7.0-beta01") api("androidx.slice:slice-builders:1.1.0-alpha02") api("androidx.slice:slice-core:1.1.0-alpha02") api("androidx.slice:slice-view:1.1.0-alpha02") - api("androidx.compose.material3:material3:1.3.0-alpha03") + api("androidx.compose.material3:material3:1.3.0-alpha06") api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion") api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion") api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion") api("androidx.lifecycle:lifecycle-livedata-ktx") api("androidx.lifecycle:lifecycle-runtime-compose") - api("androidx.navigation:navigation-compose:2.8.0-alpha05") + api("androidx.navigation:navigation-compose:2.8.0-alpha08") api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha") api("com.google.android.material:material:1.11.0") debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion") diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt index 88ba4b07d30a..1a10bf021f47 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt @@ -27,14 +27,13 @@ import androidx.compose.runtime.remember import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.LinkAnnotation import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextDecoration -const val URL_SPAN_TAG = "URL_SPAN_TAG" - @Composable fun annotatedStringResource(@StringRes id: Int): AnnotatedString { val resources = LocalContext.current.resources @@ -97,12 +96,9 @@ private fun AnnotatedString.Builder.addUrlSpan( start: Int, end: Int, ) { - addStyle( - SpanStyle(color = urlSpanColor, textDecoration = TextDecoration.Underline), - start, - end, + val url = LinkAnnotation.Url( + url = urlSpan.url, + style = SpanStyle(color = urlSpanColor, textDecoration = TextDecoration.Underline), ) - if (!urlSpan.url.isNullOrEmpty()) { - addStringAnnotation(URL_SPAN_TAG, urlSpan.url, start, end) - } + addLink(url, start, end) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt index 82ac7e3bc9b2..f864fa970d0d 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt @@ -17,26 +17,17 @@ package com.android.settingslib.spa.widget.ui import androidx.annotation.StringRes -import androidx.compose.foundation.text.ClickableText import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.platform.LocalUriHandler -import com.android.settingslib.spa.framework.util.URL_SPAN_TAG import com.android.settingslib.spa.framework.util.annotatedStringResource @Composable fun AnnotatedText(@StringRes id: Int) { - val uriHandler = LocalUriHandler.current - val annotatedString = annotatedStringResource(id) - ClickableText( - text = annotatedString, + Text( + text = annotatedStringResource(id), style = MaterialTheme.typography.bodyMedium.copy( color = MaterialTheme.colorScheme.onSurfaceVariant, ), - ) { offset -> - // Gets the url at the clicked position. - annotatedString.getStringAnnotations(URL_SPAN_TAG, offset, offset) - .firstOrNull() - ?.let { uriHandler.openUri(it.item) } - } + ) } diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt index 9928355ca089..612f9e525f20 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt @@ -19,6 +19,7 @@ package com.android.settingslib.spa.framework.util import androidx.compose.material3.MaterialTheme import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.LinkAnnotation import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight @@ -36,18 +37,23 @@ class AnnotatedStringResourceTest { val composeTestRule = createComposeRule() @Test - fun testAnnotatedStringResource() { + fun annotatedStringResource() { composeTestRule.setContent { val annotatedString = annotatedStringResource(R.string.test_annotated_string_resource) - val annotations = annotatedString.getStringAnnotations(0, annotatedString.length) + val annotations = annotatedString.getLinkAnnotations(0, annotatedString.length) assertThat(annotations).containsExactly( AnnotatedString.Range( - item = "https://www.android.com/", + item = LinkAnnotation.Url( + url = "https://www.android.com/", + style = SpanStyle( + color = MaterialTheme.colorScheme.primary, + textDecoration = TextDecoration.Underline, + ), + ), start = 31, end = 35, - tag = URL_SPAN_TAG, ) ) @@ -57,14 +63,6 @@ class AnnotatedStringResourceTest { start = 22, end = 26, ), - AnnotatedString.Range( - item = SpanStyle( - color = MaterialTheme.colorScheme.primary, - textDecoration = TextDecoration.Underline, - ), - start = 31, - end = 35, - ), ) } } diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml index 1594e8e75cb6..ad5337c3982f 100644 --- a/packages/SettingsLib/res/values-af/strings.xml +++ b/packages/SettingsLib/res/values-af/strings.xml @@ -105,9 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Regs: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktief"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Gestoor"</string> - <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktief (net linkerkant)"</string> - <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktief (net regterkant)"</string> - <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktief (linkerkant en regterkant)"</string> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktief (net links)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktief (net regs)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktief (links en regs)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktief (net media). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktiewe (net media). L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, R: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> battery"</string> <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Gekoppel (steun oudiodeling). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery"</string> diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml index 0cfae920c039..1a0fec5d431a 100644 --- a/packages/SettingsLib/res/values-es-rUS/strings.xml +++ b/packages/SettingsLib/res/values-es-rUS/strings.xml @@ -125,7 +125,7 @@ <string name="bluetooth_profile_opp" msgid="6692618568149493430">"Transferencia de archivos"</string> <string name="bluetooth_profile_hid" msgid="2969922922664315866">"Dispositivo de entrada"</string> <string name="bluetooth_profile_pan" msgid="1006235139308318188">"Acceso a Internet"</string> - <string name="bluetooth_profile_pbap" msgid="2103406516858653017">"Acceso a contactos e historial de llamadas"</string> + <string name="bluetooth_profile_pbap" msgid="2103406516858653017">"Acceso a historial de llamadas y contactos"</string> <string name="bluetooth_profile_pbap_summary" msgid="402819589201138227">"Se usará la información para anuncios de llamadas y más"</string> <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"Compartir conexión a Internet"</string> <string name="bluetooth_profile_map" msgid="8907204701162107271">"Mensajes de texto"</string> @@ -281,7 +281,7 @@ <string name="keep_screen_on_summary" msgid="1510731514101925829">"La pantalla nunca quedará inactiva mientras el dispositivo se esté cargando"</string> <string name="bt_hci_snoop_log" msgid="7291287955649081448">"Registro de Bluetooth HCI"</string> <string name="bt_hci_snoop_log_summary" msgid="6808538971394092284">"Capturar paquetes de Bluetooth (activa/desactiva el Bluetooth después de cambiar esta configuración)"</string> - <string name="oem_unlock_enable" msgid="5334869171871566731">"Desbloqueo de OEM"</string> + <string name="oem_unlock_enable" msgid="5334869171871566731">"Desbloqueo para OEM"</string> <string name="oem_unlock_enable_summary" msgid="5857388174390953829">"Permitir que el cargador de inicio se desbloquee"</string> <string name="confirm_enable_oem_unlock_title" msgid="8249318129774367535">"¿Permitir desbloqueo de OEM?"</string> <string name="confirm_enable_oem_unlock_text" msgid="854131050791011970">"ADVERTENCIA: Las funciones de protección de dispositivos no funcionarán en este dispositivo mientras esta configuración esté activada."</string> diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml index 497fbbb13a67..c330b7684fe5 100644 --- a/packages/SettingsLib/res/values-eu/strings.xml +++ b/packages/SettingsLib/res/values-eu/strings.xml @@ -294,7 +294,7 @@ <string name="wifi_scan_throttling" msgid="2985624788509913617">"Wifi-sareen bilaketaren moteltzea"</string> <string name="wifi_non_persistent_mac_randomization" msgid="7482769677894247316">"Wifi-konexioetan iraunkorrak ez diren MAC helbideak ausaz antolatzea"</string> <string name="mobile_data_always_on" msgid="8275958101875563572">"Datu-konexioa beti aktibo"</string> - <string name="tethering_hardware_offload" msgid="4116053719006939161">"Konexioa partekatzeko hardwarearen azelerazioa"</string> + <string name="tethering_hardware_offload" msgid="4116053719006939161">"Konexioa partekatzeko hardwarearen bizkortzea"</string> <string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Erakutsi Bluetooth bidezko gailuak izenik gabe"</string> <string name="bluetooth_disable_absolute_volume" msgid="1452342324349203434">"Desgaitu bolumen absolutua"</string> <string name="bluetooth_enable_gabeldorsche" msgid="9131730396242883416">"Gaitu Gabeldorsche"</string> @@ -339,7 +339,7 @@ <string name="allow_mock_location_summary" msgid="179780881081354579">"Onartu kokapen faltsuak"</string> <string name="debug_view_attributes" msgid="3539609843984208216">"Gaitu ikuspegiaren atributuak ikuskatzeko aukera"</string> <string name="mobile_data_always_on_summary" msgid="1112156365594371019">"Mantendu datu-konexioa beti aktibo, baita wifi-konexioa aktibo dagoenean ere (sare batetik bestera bizkor aldatu ahal izateko)."</string> - <string name="tethering_hardware_offload_summary" msgid="7801345335142803029">"Erabilgarri badago, erabili konexioa partekatzeko hardwarearen azelerazioa"</string> + <string name="tethering_hardware_offload_summary" msgid="7801345335142803029">"Erabilgarri badago, erabili konexioa partekatzeko hardwarearen bizkortzea"</string> <string name="adb_warning_title" msgid="7708653449506485728">"USB bidezko arazketa onartu?"</string> <string name="adb_warning_message" msgid="8145270656419669221">"USB bidezko arazketa garapen-xedeetarako soilik dago diseinatuta. Erabil ezazu ordenagailuaren eta gailuaren artean datuak kopiatzeko, aplikazioak gailuan jakinarazi gabe instalatzeko eta erregistro-datuak irakurtzeko."</string> <string name="adbwifi_warning_title" msgid="727104571653031865">"Hari gabeko arazketa baimendu nahi duzu?"</string> diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml index 5e7f998a7e41..1d331fbf390c 100644 --- a/packages/SettingsLib/res/values-fa/strings.xml +++ b/packages/SettingsLib/res/values-fa/strings.xml @@ -96,8 +96,8 @@ <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"متصل (بدون تلفن یا رسانه)، باتری <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"فعال. باتری: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"فعال. باتری چپ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>، باتری راست: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> - <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"فعال. باتری چپ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> - <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"فعال. باتری راست: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"فعال. چپ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> باتری."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"فعال. راست: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> باتری."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> شارژ باتری"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"باتری <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"باتری چپ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>، باتری راست: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml index e434d1efaabf..cfb6546057ca 100644 --- a/packages/SettingsLib/res/values-hi/strings.xml +++ b/packages/SettingsLib/res/values-hi/strings.xml @@ -126,7 +126,7 @@ <string name="bluetooth_profile_hid" msgid="2969922922664315866">"इनपुट डिवाइस"</string> <string name="bluetooth_profile_pan" msgid="1006235139308318188">"इंटरनेट का ऐक्सेस"</string> <string name="bluetooth_profile_pbap" msgid="2103406516858653017">"संपर्क और कॉल इतिहास का ऐक्सेस दें"</string> - <string name="bluetooth_profile_pbap_summary" msgid="402819589201138227">"इस जानकारी का इस्तेमाल, कॉल की सूचना देने और दूसरी चीज़ों के लिए किया जाएगा"</string> + <string name="bluetooth_profile_pbap_summary" msgid="402819589201138227">"इस जानकारी का इस्तेमाल, कॉल की सूचना देने वगैरह के लिए किया जाएगा"</string> <string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"इंटरनेट कनेक्शन साझाकरण"</string> <string name="bluetooth_profile_map" msgid="8907204701162107271">"लेख संदेश"</string> <string name="bluetooth_profile_sap" msgid="8304170950447934386">"सिम का ऐक्सेस"</string> diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml index 4c0712c74ad1..5e3d779c34a2 100644 --- a/packages/SettingsLib/res/values-hy/strings.xml +++ b/packages/SettingsLib/res/values-hy/strings.xml @@ -159,7 +159,7 @@ <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"Չեղարկել"</string> <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"Զուգակցում է մուտքի թույլտվությունը դեպի ձեր կոնտակտները և զանգերի պատմությունը, երբ միացված է:"</string> <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"Չհաջողվեց զուգակցել <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի հետ:"</string> - <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Չհաջողվեց զուգակցել <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի հետ սխալ PIN-ի կամ անցաբառի պատճառով:"</string> + <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Չհաջողվեց զուգակցել <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի հետ՝ սխալ PIN-ի կամ անցաբառի պատճառով:"</string> <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"Հնարավոր չէ կապ հաստատել <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի հետ:"</string> <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Զուգավորումը մերժվեց <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի կողմից:"</string> <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Համակարգիչ"</string> diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml index 4cd076b77fb9..be8d039ec07d 100644 --- a/packages/SettingsLib/res/values-mk/strings.xml +++ b/packages/SettingsLib/res/values-mk/strings.xml @@ -94,7 +94,7 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Поврзан со <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (без телефон), ниво на батеријата <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_connected_no_a2dp_battery_level" msgid="6499078454894324287">"Поврзан со <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (без аудиовизуелни содржини), ниво на батеријата <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Поврзан со <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (без телефон и аудиовизуелни содржини), ниво на батеријата <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Активно. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерија."</string> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Активно. Батерија: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Активно. Л: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> батерија, Д: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> батерија."</string> <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Активно. Л: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерија."</string> <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Активно. Д: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерија."</string> @@ -159,7 +159,7 @@ <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"Откажи"</string> <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"Кога е поврзано, спарувањето одобрува пристап до контактите и историјата на повиците."</string> <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"Не може да се спари со <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string> - <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Не може да се спари со <xliff:g id="DEVICE_NAME">%1$s</xliff:g> поради погрешен PIN или лозинка."</string> + <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Не може да се спари со <xliff:g id="DEVICE_NAME">%1$s</xliff:g> поради погрешен PIN или криптографски клуч."</string> <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"Не може да комуницира со <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string> <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Спарувањето е одбиено од <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string> <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Компјутер"</string> diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml index 42797d40ebaf..66efe8b3cf9a 100644 --- a/packages/SettingsLib/res/values-mn/strings.xml +++ b/packages/SettingsLib/res/values-mn/strings.xml @@ -94,7 +94,7 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Холбогдсон (утас байхгүй), батарей <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> <string name="bluetooth_connected_no_a2dp_battery_level" msgid="6499078454894324287">"Холбогдсон (медиа байхгүй), батарей <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Холбогдсон (утас эсвэл медиа байхгүй), батарей <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> - <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Идэвхтэй байна. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батарей."</string> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Идэвхтэй байна. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> цэнэгтэй."</string> <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Идэвхтэй байна. З: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Б: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> батарей."</string> <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Идэвхтэй байна. З: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батарей."</string> <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Идэвхтэй байна. Б: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батарей."</string> diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml index 40d604e39954..8bafef3caf4f 100644 --- a/packages/SettingsLib/res/values-ne/strings.xml +++ b/packages/SettingsLib/res/values-ne/strings.xml @@ -157,7 +157,7 @@ <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"कनेक्ट गर्नुहोस्"</string> <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"जोडी"</string> <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"रद्द गर्नुहोस्"</string> - <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"जब जडान हुन्छ जोडी अनुदानले तपाईँको सम्पर्कहरू पहुँच गर्छ र इतिहास सम्झाउँछ।"</string> + <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"जब जडान हुन्छ जोडी अनुदानले तपाईँको कन्ट्याक्टहरू पहुँच गर्छ र इतिहास सम्झाउँछ।"</string> <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g>सँग जोडा मिलाउन सकेन"</string> <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"गलत PIN वा पासकीका कारणले <xliff:g id="DEVICE_NAME">%1$s</xliff:g> मा कनेक्ट गर्न सकिएन।"</string> <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> सँग कुराकानी हुन सक्दैन।"</string> diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml index 422d0f2c6256..d054d9b08d68 100644 --- a/packages/SettingsLib/res/values-pt-rBR/strings.xml +++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml @@ -110,11 +110,11 @@ <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Ativo (esquerdo e direito)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Ativo (apenas mídia). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Ativo (apenas mídia). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria. Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string> - <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Conectado (com suporte ao compartilhamento de áudio). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> - <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Conectado (com suporte ao compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria. Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string> - <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Conectado (com suporte ao compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> - <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Conectado (com suporte ao compartilhamento de áudio). Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> - <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Conectado (com suporte ao compartilhamento de áudio)"</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Conectado (aceita compartilhamento de áudio). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Conectado (aceita compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria. Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Conectado (aceita compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Conectado (aceita compartilhamento de áudio). Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Conectado (aceita compartilhamento de áudio)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Ativo (apenas mídia)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Com suporte ao compartilhamento de áudio"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Ativo (apenas mídia), apenas esquerdo"</string> diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml index 422d0f2c6256..d054d9b08d68 100644 --- a/packages/SettingsLib/res/values-pt/strings.xml +++ b/packages/SettingsLib/res/values-pt/strings.xml @@ -110,11 +110,11 @@ <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Ativo (esquerdo e direito)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Ativo (apenas mídia). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Ativo (apenas mídia). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria. Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string> - <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Conectado (com suporte ao compartilhamento de áudio). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> - <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Conectado (com suporte ao compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria. Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string> - <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Conectado (com suporte ao compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> - <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Conectado (com suporte ao compartilhamento de áudio). Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> - <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Conectado (com suporte ao compartilhamento de áudio)"</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Conectado (aceita compartilhamento de áudio). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Conectado (aceita compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria. Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Conectado (aceita compartilhamento de áudio). Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Conectado (aceita compartilhamento de áudio). Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Conectado (aceita compartilhamento de áudio)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Ativo (apenas mídia)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Com suporte ao compartilhamento de áudio"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Ativo (apenas mídia), apenas esquerdo"</string> diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml index 54c9b0a71980..aaf648ea98a5 100644 --- a/packages/SettingsLib/res/values-ru/strings.xml +++ b/packages/SettingsLib/res/values-ru/strings.xml @@ -94,7 +94,7 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Подключено (кроме звонков), уровень заряда батареи: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> <string name="bluetooth_connected_no_a2dp_battery_level" msgid="6499078454894324287">"Подключено (кроме аудио), уровень заряда батареи: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Подключено (кроме звонков и аудио), уровень заряда батареи: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> - <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Используется, заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Используется, заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Используется, заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> (Л), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> (П)."</string> <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Используется, заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (Л)"</string> <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Используется, заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (П)"</string> diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml index 4286c8144fb0..ed0b1dffd086 100644 --- a/packages/SettingsLib/res/values-ur/strings.xml +++ b/packages/SettingsLib/res/values-ur/strings.xml @@ -159,7 +159,7 @@ <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"منسوخ کریں"</string> <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"منسلک ہونے پر جوڑا بنانے سے آپ کے رابطوں اور کال کی سرگزشت تک رسائی حاصل ہو جاتی ہے۔"</string> <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> کے ساتھ جوڑا نہیں بنا سکا۔"</string> - <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"غلط PIN یا پاس کلید کی وجہ سے <xliff:g id="DEVICE_NAME">%1$s</xliff:g> کے ساتھ جوڑا نہیں بنا سکا۔"</string> + <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"غلط PIN یا پاس کلید کی وجہ سے <xliff:g id="DEVICE_NAME">%1$s</xliff:g> کے ساتھ جوڑا نہیں بنایا جا سکا۔"</string> <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> کے ساتھ مواصلت نہیں ہو سکتی۔"</string> <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> نے جوڑا بنانے کو مسترد کر دیا۔"</string> <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"کمپیوٹر"</string> diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml index 56282ec6fed1..25ca0c214d69 100644 --- a/packages/SettingsLib/res/values-zh-rCN/strings.xml +++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml @@ -706,9 +706,9 @@ <string name="physical_keyboard_title" msgid="4811935435315835220">"实体键盘"</string> <string name="keyboard_layout_dialog_title" msgid="3927180147005616290">"选择键盘布局"</string> <string name="keyboard_layout_default_label" msgid="1997292217218546957">"默认"</string> - <string name="turn_screen_on_title" msgid="3266937298097573424">"开启屏幕"</string> + <string name="turn_screen_on_title" msgid="3266937298097573424">"唤醒屏幕"</string> <string name="allow_turn_screen_on" msgid="6194845766392742639">"允许开启屏幕"</string> - <string name="allow_turn_screen_on_description" msgid="43834403291575164">"允许应用开启屏幕。如获授权,该应用便可在您未明确表达意愿的情况下随时开启屏幕。"</string> + <string name="allow_turn_screen_on_description" msgid="43834403291575164">"允许应用唤醒屏幕。如获授权,该应用便可在您未明确表达意愿的情况下随时唤醒屏幕。"</string> <string name="bt_le_audio_broadcast_dialog_title" msgid="5392738488989777074">"要停止广播“<xliff:g id="APP_NAME">%1$s</xliff:g>”的内容吗?"</string> <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"如果广播“<xliff:g id="SWITCHAPP">%1$s</xliff:g>”的内容或更改输出来源,当前的广播就会停止"</string> <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"广播“<xliff:g id="SWITCHAPP">%1$s</xliff:g>”的内容"</string> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index adbfc72cd684..4ea746007f76 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1712,7 +1712,7 @@ <string name="keyboard_layout_default_label">Default</string> <!-- Special access > Title for managing turn screen on settings. [CHAR LIMIT=50] --> - <string name="turn_screen_on_title">Turn screen on</string> + <string name="turn_screen_on_title">Screen turn-on control</string> <!-- Label for a setting which controls whether an app can turn the screen on [CHAR LIMIT=45] --> <string name="allow_turn_screen_on">Allow turning the screen on</string> <!-- Description for a setting which controls whether an app can turn the screen on [CHAR LIMIT=NONE] --> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index 3dffb275f917..89174125a296 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java @@ -10,8 +10,10 @@ import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothStatusCodes; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Bitmap; @@ -37,12 +39,9 @@ import com.android.settingslib.flags.Flags; import com.android.settingslib.widget.AdaptiveIcon; import com.android.settingslib.widget.AdaptiveOutlineDrawable; -import com.google.common.collect.ImmutableSet; - import java.io.IOException; import java.util.List; import java.util.Locale; -import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -56,8 +55,6 @@ public class BluetoothUtils { public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled"; private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25; private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH"; - private static final Set<String> EXCLUSIVE_MANAGERS = - ImmutableSet.of("com.google.android.gms.dck"); private static ErrorListener sErrorListener; @@ -740,14 +737,13 @@ public class BluetoothUtils { /** * Returns the BluetoothDevice's exclusive manager ({@link - * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists and is in the given - * set, otherwise null. + * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata) if it exists, otherwise null. */ @Nullable - private static String getAllowedExclusiveManager(BluetoothDevice bluetoothDevice) { - byte[] exclusiveManagerNameBytes = + private static String getExclusiveManager(BluetoothDevice bluetoothDevice) { + byte[] exclusiveManagerBytes = bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER); - if (exclusiveManagerNameBytes == null) { + if (exclusiveManagerBytes == null) { Log.d( TAG, "Bluetooth device " @@ -755,47 +751,46 @@ public class BluetoothUtils { + " doesn't have exclusive manager"); return null; } - String exclusiveManagerName = new String(exclusiveManagerNameBytes); - return getExclusiveManagers().contains(exclusiveManagerName) ? exclusiveManagerName : null; + return new String(exclusiveManagerBytes); } - /** Checks if given package is installed */ - private static boolean isPackageInstalled(Context context, String packageName) { + /** Checks if given package is installed and enabled */ + private static boolean isPackageInstalledAndEnabled(Context context, String packageName) { PackageManager packageManager = context.getPackageManager(); try { - packageManager.getPackageInfo(packageName, 0); - return true; + ApplicationInfo appInfo = packageManager.getApplicationInfo(packageName, 0); + return appInfo.enabled; } catch (PackageManager.NameNotFoundException e) { - Log.d(TAG, "Package " + packageName + " is not installed"); + Log.d(TAG, "Package " + packageName + " is not installed/enabled"); } return false; } /** * A BluetoothDevice is exclusively managed if 1) it has field {@link - * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata. 2) the exclusive manager app name is - * in the allowlist. 3) the exclusive manager app is installed. + * BluetoothDevice.METADATA_EXCLUSIVE_MANAGER} in metadata. 2) the exclusive manager app is + * installed and enabled. */ public static boolean isExclusivelyManagedBluetoothDevice( @NonNull Context context, @NonNull BluetoothDevice bluetoothDevice) { - String exclusiveManagerName = getAllowedExclusiveManager(bluetoothDevice); + String exclusiveManagerName = getExclusiveManager(bluetoothDevice); if (exclusiveManagerName == null) { return false; } - if (!isPackageInstalled(context, exclusiveManagerName)) { + + ComponentName exclusiveManagerComponent = + ComponentName.unflattenFromString(exclusiveManagerName); + String exclusiveManagerPackage = exclusiveManagerComponent != null + ? exclusiveManagerComponent.getPackageName() : exclusiveManagerName; + + if (!isPackageInstalledAndEnabled(context, exclusiveManagerPackage)) { return false; } else { - Log.d(TAG, "Found exclusively managed app " + exclusiveManagerName); + Log.d(TAG, "Found exclusively managed app " + exclusiveManagerPackage); return true; } } - /** Return the allowlist for exclusive manager names. */ - @NonNull - public static Set<String> getExclusiveManagers() { - return EXCLUSIVE_MANAGERS; - } - /** * Get CSIP group id for {@link CachedBluetoothDevice}. * diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 30bec7724dd5..c2a83b1e772f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -68,6 +68,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; @@ -1442,14 +1443,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> int stringRes = R.string.bluetooth_pairing; //when profile is connected, information would be available if (profileConnected) { - // Update Meta data for connected device - if (BluetoothUtils.getBooleanMetaData( - mDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) { - leftBattery = BluetoothUtils.getIntMetaData(mDevice, - BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY); - rightBattery = BluetoothUtils.getIntMetaData(mDevice, - BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY); - } + leftBattery = getLeftBatteryLevel(); + rightBattery = getRightBatteryLevel(); // Set default string with battery level in device connected situation. if (isTwsBatteryAvailable(leftBattery, rightBattery)) { @@ -1485,7 +1480,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> boolean isActiveLeAudioHearingAid = mIsActiveDeviceLeAudio && isConnectedHapClientDevice(); if (isActiveAshaHearingAid || isActiveLeAudioHearingAid) { - return getHearingDeviceSummary(leftBattery, rightBattery, shortSummary); + stringRes = getHearingDeviceSummaryRes(leftBattery, rightBattery, shortSummary); } } } @@ -1498,6 +1493,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> boolean summaryIncludesBatteryLevel = stringRes == R.string.bluetooth_battery_level || stringRes == R.string.bluetooth_active_battery_level || stringRes == R.string.bluetooth_active_battery_level_untethered + || stringRes == R.string.bluetooth_active_battery_level_untethered_left + || stringRes == R.string.bluetooth_active_battery_level_untethered_right || stringRes == R.string.bluetooth_battery_level_untethered; if (isTvSummary && summaryIncludesBatteryLevel && Flags.enableTvMediaOutputDialog()) { return getTvBatterySummary( @@ -1510,6 +1507,14 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> if (isTwsBatteryAvailable(leftBattery, rightBattery)) { return mContext.getString(stringRes, Utils.formatPercentage(leftBattery), Utils.formatPercentage(rightBattery)); + } else if (leftBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN + && !BluetoothUtils.getBooleanMetaData(mDevice, + BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) { + return mContext.getString(stringRes, Utils.formatPercentage(leftBattery)); + } else if (rightBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN + && !BluetoothUtils.getBooleanMetaData(mDevice, + BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) { + return mContext.getString(stringRes, Utils.formatPercentage(rightBattery)); } else { return mContext.getString(stringRes, batteryLevelPercentageString); } @@ -1553,60 +1558,34 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> return spannableBuilder; } - private CharSequence getHearingDeviceSummary(int leftBattery, int rightBattery, + private int getHearingDeviceSummaryRes(int leftBattery, int rightBattery, boolean shortSummary) { + boolean isLeftDeviceConnected = getConnectedHearingAidSide( + HearingAidInfo.DeviceSide.SIDE_LEFT).isPresent(); + boolean isRightDeviceConnected = getConnectedHearingAidSide( + HearingAidInfo.DeviceSide.SIDE_RIGHT).isPresent(); + boolean shouldShowLeftBattery = + !shortSummary && (leftBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN); + boolean shouldShowRightBattery = + !shortSummary && (rightBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN); - CachedBluetoothDevice memberDevice = getMemberDevice().stream().filter( - CachedBluetoothDevice::isConnected).findFirst().orElse(null); - if (memberDevice == null && mSubDevice != null && mSubDevice.isConnected()) { - memberDevice = mSubDevice; - } - - CachedBluetoothDevice leftDevice = null; - CachedBluetoothDevice rightDevice = null; - final int deviceSide = getDeviceSide(); - if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT) { - leftDevice = this; - rightDevice = memberDevice; - } else if (deviceSide == HearingAidInfo.DeviceSide.SIDE_RIGHT) { - leftDevice = memberDevice; - rightDevice = this; - } else if (deviceSide == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT) { - leftDevice = this; - rightDevice = this; + if (isLeftDeviceConnected && isRightDeviceConnected) { + return (shouldShowLeftBattery && shouldShowRightBattery) + ? R.string.bluetooth_active_battery_level_untethered + : R.string.bluetooth_hearing_aid_left_and_right_active; } - - if (leftBattery < 0 && leftDevice != null) { - leftBattery = leftDevice.getBatteryLevel(); + if (isLeftDeviceConnected) { + return shouldShowLeftBattery + ? R.string.bluetooth_active_battery_level_untethered_left + : R.string.bluetooth_hearing_aid_left_active; } - if (rightBattery < 0 && rightDevice != null) { - rightBattery = rightDevice.getBatteryLevel(); - } - - if (leftDevice != null && rightDevice != null) { - if (leftBattery >= 0 && rightBattery >= 0 && !shortSummary) { - return mContext.getString(R.string.bluetooth_active_battery_level_untethered, - Utils.formatPercentage(leftBattery), Utils.formatPercentage(rightBattery)); - } else { - return mContext.getString(R.string.bluetooth_hearing_aid_left_and_right_active); - } - } else if (leftDevice != null) { - if (leftBattery >= 0 && !shortSummary) { - return mContext.getString(R.string.bluetooth_active_battery_level_untethered_left, - Utils.formatPercentage(leftBattery)); - } else { - return mContext.getString(R.string.bluetooth_hearing_aid_left_active); - } - } else if (rightDevice != null) { - if (rightBattery >= 0 && !shortSummary) { - return mContext.getString(R.string.bluetooth_active_battery_level_untethered_right, - Utils.formatPercentage(rightBattery)); - } else { - return mContext.getString(R.string.bluetooth_hearing_aid_right_active); - } + if (isRightDeviceConnected) { + return shouldShowRightBattery + ? R.string.bluetooth_active_battery_level_untethered_right + : R.string.bluetooth_hearing_aid_right_active; } - return mContext.getString(R.string.bluetooth_active_no_battery_level); + return R.string.bluetooth_active_no_battery_level; } private void addBatterySpan(SpannableStringBuilder builder, @@ -1632,6 +1611,56 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> return leftBattery >= 0 && rightBattery >= 0; } + private Optional<CachedBluetoothDevice> getConnectedHearingAidSide( + @HearingAidInfo.DeviceSide int side) { + return Stream.concat(Stream.of(this, mSubDevice), mMemberDevices.stream()) + .filter(Objects::nonNull) + .filter(device -> device.getDeviceSide() == side + || device.getDeviceSide() == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT) + .filter(device -> device.getDevice().isConnected()) + // For hearing aids, we should expect only one device assign to one side, but if + // it happens, we don't care which one. + .findAny(); + } + + private int getLeftBatteryLevel() { + int leftBattery = BluetoothDevice.BATTERY_LEVEL_UNKNOWN; + if (BluetoothUtils.getBooleanMetaData(mDevice, + BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) { + leftBattery = BluetoothUtils.getIntMetaData(mDevice, + BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY); + } + + // Retrieve hearing aids (ASHA, HAP) individual side battery level + if (leftBattery == BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { + leftBattery = getConnectedHearingAidSide(HearingAidInfo.DeviceSide.SIDE_LEFT) + .map(CachedBluetoothDevice::getBatteryLevel) + .filter(batteryLevel -> batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN) + .orElse(BluetoothDevice.BATTERY_LEVEL_UNKNOWN); + } + + return leftBattery; + } + + private int getRightBatteryLevel() { + int rightBattery = BluetoothDevice.BATTERY_LEVEL_UNKNOWN; + if (BluetoothUtils.getBooleanMetaData( + mDevice, BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) { + rightBattery = BluetoothUtils.getIntMetaData(mDevice, + BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY); + } + + // Retrieve hearing aids (ASHA, HAP) individual side battery level + if (rightBattery == BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { + rightBattery = getConnectedHearingAidSide(HearingAidInfo.DeviceSide.SIDE_RIGHT) + .map(CachedBluetoothDevice::getBatteryLevel) + .filter(batteryLevel -> batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN) + .orElse(BluetoothDevice.BATTERY_LEVEL_UNKNOWN); + } + + return rightBattery; + } + private boolean isProfileConnectedFail() { Log.d(TAG, "anonymizedAddress=" + mDevice.getAnonymizedAddress() + " mIsA2dpProfileConnectedFail=" + mIsA2dpProfileConnectedFail diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java index 822a60889931..1040ac6bc860 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java @@ -36,6 +36,7 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; +import androidx.annotation.GuardedBy; import androidx.annotation.VisibleForTesting; import com.android.internal.telephony.SmsApplication; @@ -56,13 +57,22 @@ public class PowerAllowlistBackend { private static PowerAllowlistBackend sInstance; + private final Object mAllowlistedAppsLock = new Object(); + private final Object mSysAllowlistedAppsLock = new Object(); + private final Object mDefaultActiveAppsLock = new Object(); + private final Context mAppContext; private final IDeviceIdleController mDeviceIdleService; + + @GuardedBy("mAllowlistedAppsLock") private final ArraySet<String> mAllowlistedApps = new ArraySet<>(); + @GuardedBy("mSysAllowlistedAppsLock") private final ArraySet<String> mSysAllowlistedApps = new ArraySet<>(); + @GuardedBy("mDefaultActiveAppsLock") private final ArraySet<String> mDefaultActiveApps = new ArraySet<>(); - public PowerAllowlistBackend(Context context) { + @VisibleForTesting + PowerAllowlistBackend(Context context) { this(context, IDeviceIdleController.Stub.asInterface( ServiceManager.getService(DEVICE_IDLE_SERVICE))); } @@ -75,24 +85,25 @@ public class PowerAllowlistBackend { } public int getAllowlistSize() { - return mAllowlistedApps.size(); + synchronized (mAllowlistedAppsLock) { + return mAllowlistedApps.size(); + } } - /** - * Check if target package is in System allow list - */ + /** Check if target package is in System allow list */ public boolean isSysAllowlisted(String pkg) { - return mSysAllowlistedApps.contains(pkg); + synchronized (mSysAllowlistedAppsLock) { + return mSysAllowlistedApps.contains(pkg); + } } - /** - * Check if target package is in allow list - */ + /** Check if target package is in allow list */ public boolean isAllowlisted(String pkg, int uid) { - if (mAllowlistedApps.contains(pkg)) { - return true; + synchronized (mAllowlistedAppsLock) { + if (mAllowlistedApps.contains(pkg)) { + return true; + } } - if (isDefaultActiveApp(pkg, uid)) { return true; } @@ -100,16 +111,16 @@ public class PowerAllowlistBackend { return false; } - /** - * Check if it is default active app in multiple area(i.e. SMS, Dialer, Device admin..) - */ + /** Check if it is default active app in multiple area */ public boolean isDefaultActiveApp(String pkg, int uid) { // Additionally, check if pkg is default dialer/sms. They are considered essential apps and // should be automatically allowlisted (otherwise user may be able to set restriction on // them, leading to bad device behavior.) - if (mDefaultActiveApps.contains(pkg)) { - return true; + synchronized (mDefaultActiveAppsLock) { + if (mDefaultActiveApps.contains(pkg)) { + return true; + } } final DevicePolicyManager devicePolicyManager = mAppContext.getSystemService( @@ -143,9 +154,7 @@ public class PowerAllowlistBackend { DEFAULT_SYSTEM_EXEMPT_POWER_RESTRICTIONS_ENABLED); } - /** - * Check if target package is in allow list except idle app - */ + /** Check if target package is in allow list except idle app */ public boolean isAllowlistedExceptIdle(String pkg) { try { return mDeviceIdleService.isPowerSaveWhitelistExceptIdleApp(pkg); @@ -156,6 +165,7 @@ public class PowerAllowlistBackend { } /** + * Check if target package is in allow list except idle app * * @param pkgs a list of packageName * @return true when one of package is in allow list @@ -174,20 +184,21 @@ public class PowerAllowlistBackend { } /** - * Add app into power save allow list. + * Add app into power save allow list + * * @param pkg packageName of the app */ - // TODO: Fix all callers to pass in UID public void addApp(String pkg) { addApp(pkg, Process.INVALID_UID); } /** - * Add app into power save allow list. + * Add app into power save allow list + * * @param pkg packageName of the app * @param uid uid of the app */ - public void addApp(String pkg, int uid) { + public synchronized void addApp(String pkg, int uid) { try { if (android.app.Flags.appRestrictionsApi()) { if (uid == Process.INVALID_UID) { @@ -204,7 +215,9 @@ public class PowerAllowlistBackend { } mDeviceIdleService.addPowerSaveWhitelistApp(pkg); - mAllowlistedApps.add(pkg); + synchronized (mAllowlistedAppsLock) { + mAllowlistedApps.add(pkg); + } } catch (RemoteException e) { Log.w(TAG, "Unable to reach IDeviceIdleController", e); } catch (NameNotFoundException e) { @@ -213,7 +226,8 @@ public class PowerAllowlistBackend { } /** - * Remove package from power save allow list. + * Remove package from power save allow list + * * @param pkg packageName of the app */ public void removeApp(String pkg) { @@ -222,10 +236,11 @@ public class PowerAllowlistBackend { /** * Remove package from power save allow list. + * * @param pkg packageName of the app * @param uid uid of the app */ - public void removeApp(String pkg, int uid) { + public synchronized void removeApp(String pkg, int uid) { try { if (android.app.Flags.appRestrictionsApi()) { if (uid == Process.INVALID_UID) { @@ -241,7 +256,9 @@ public class PowerAllowlistBackend { } mDeviceIdleService.removePowerSaveWhitelistApp(pkg); - mAllowlistedApps.remove(pkg); + synchronized (mAllowlistedAppsLock) { + mAllowlistedApps.remove(pkg); + } } catch (RemoteException e) { Log.w(TAG, "Unable to reach IDeviceIdleController", e); } catch (NameNotFoundException e) { @@ -249,25 +266,33 @@ public class PowerAllowlistBackend { } } - /** - * Refresh all of lists - */ + /** Refresh all of lists */ @VisibleForTesting - public void refreshList() { - mSysAllowlistedApps.clear(); - mAllowlistedApps.clear(); - mDefaultActiveApps.clear(); + public synchronized void refreshList() { + synchronized (mSysAllowlistedAppsLock) { + mSysAllowlistedApps.clear(); + } + synchronized (mAllowlistedAppsLock) { + mAllowlistedApps.clear(); + } + synchronized (mDefaultActiveAppsLock) { + mDefaultActiveApps.clear(); + } if (mDeviceIdleService == null) { return; } try { final String[] allowlistedApps = mDeviceIdleService.getFullPowerWhitelist(); - for (String app : allowlistedApps) { - mAllowlistedApps.add(app); + synchronized (mAllowlistedAppsLock) { + for (String app : allowlistedApps) { + mAllowlistedApps.add(app); + } } final String[] sysAllowlistedApps = mDeviceIdleService.getSystemPowerWhitelist(); - for (String app : sysAllowlistedApps) { - mSysAllowlistedApps.add(app); + synchronized (mSysAllowlistedAppsLock) { + for (String app : sysAllowlistedApps) { + mSysAllowlistedApps.add(app); + } } final boolean hasTelephony = mAppContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_TELEPHONY); @@ -278,26 +303,28 @@ public class PowerAllowlistBackend { if (hasTelephony) { if (defaultSms != null) { - mDefaultActiveApps.add(defaultSms.getPackageName()); + synchronized (mDefaultActiveAppsLock) { + mDefaultActiveApps.add(defaultSms.getPackageName()); + } } if (!TextUtils.isEmpty(defaultDialer)) { - mDefaultActiveApps.add(defaultDialer); + synchronized (mDefaultActiveAppsLock) { + mDefaultActiveApps.add(defaultDialer); + } } } - } catch (RemoteException e) { - Log.w(TAG, "Unable to reach IDeviceIdleController", e); + } catch (Exception e) { + Log.e(TAG, "Failed to invoke refreshList()", e); } } - /** - * @param context - * @return a PowerAllowlistBackend object - */ + /** Get the {@link PowerAllowlistBackend} instance */ public static PowerAllowlistBackend getInstance(Context context) { - if (sInstance == null) { - sInstance = new PowerAllowlistBackend(context); + synchronized (PowerAllowlistBackend.class) { + if (sInstance == null) { + sInstance = new PowerAllowlistBackend(context); + } + return sInstance; } - return sInstance; } - } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java index f197f9ee0baf..7a2818dbf299 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java @@ -28,7 +28,7 @@ import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.content.Context; -import android.content.pm.PackageInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.media.AudioManager; @@ -80,7 +80,8 @@ public class BluetoothUtilsTest { private static final String CONTROL_METADATA = "<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" + STRING_METADATA + "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>"; - private static final String FAKE_EXCLUSIVE_MANAGER_NAME = "com.fake.name"; + private static final String TEST_EXCLUSIVE_MANAGER_PACKAGE = "com.test.manager"; + private static final String TEST_EXCLUSIVE_MANAGER_COMPONENT = "com.test.manager/.component"; @Before public void setUp() { @@ -399,7 +400,7 @@ public class BluetoothUtilsTest { } @Test - public void isExclusivelyManagedBluetoothDevice_isNotExclusivelyManaged_returnFalse() { + public void isExclusivelyManaged_hasNoManager_returnFalse() { when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( null); @@ -408,45 +409,85 @@ public class BluetoothUtilsTest { } @Test - public void isExclusivelyManagedBluetoothDevice_isNotInAllowList_returnFalse() { + public void isExclusivelyManaged_hasPackageName_packageNotInstalled_returnFalse() + throws Exception { when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( - FAKE_EXCLUSIVE_MANAGER_NAME.getBytes()); + TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes()); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager) + .getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0); assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, mBluetoothDevice)).isEqualTo(false); } @Test - public void isExclusivelyManagedBluetoothDevice_packageNotInstalled_returnFalse() + public void isExclusivelyManaged_hasComponentName_packageNotInstalled_returnFalse() throws Exception { - final String exclusiveManagerName = - BluetoothUtils.getExclusiveManagers().stream().findAny().orElse( - FAKE_EXCLUSIVE_MANAGER_NAME); + when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( + TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes()); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager) + .getApplicationInfo(TEST_EXCLUSIVE_MANAGER_PACKAGE, 0); + + assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, + mBluetoothDevice)).isEqualTo(false); + } + @Test + public void isExclusivelyManaged_hasPackageName_packageNotEnabled_returnFalse() + throws Exception { + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.enabled = false; + when(mContext.getPackageManager()).thenReturn(mPackageManager); + doReturn(appInfo).when(mPackageManager).getApplicationInfo( + TEST_EXCLUSIVE_MANAGER_PACKAGE, 0); when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( - exclusiveManagerName.getBytes()); + TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes()); + + assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, + mBluetoothDevice)).isEqualTo(false); + } + + @Test + public void isExclusivelyManaged_hasComponentName_packageNotEnabled_returnFalse() + throws Exception { + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.enabled = false; when(mContext.getPackageManager()).thenReturn(mPackageManager); - doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager).getPackageInfo( - exclusiveManagerName, 0); + doReturn(appInfo).when(mPackageManager).getApplicationInfo( + TEST_EXCLUSIVE_MANAGER_PACKAGE, 0); + when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( + TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes()); assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, - mBluetoothDevice)).isEqualTo(false); + mBluetoothDevice)).isEqualTo(false); } @Test - public void isExclusivelyManagedBluetoothDevice_isExclusivelyManaged_returnTrue() + public void isExclusivelyManaged_hasPackageName_packageInstalledAndEnabled_returnTrue() throws Exception { - final String exclusiveManagerName = - BluetoothUtils.getExclusiveManagers().stream().findAny().orElse( - FAKE_EXCLUSIVE_MANAGER_NAME); + when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( + TEST_EXCLUSIVE_MANAGER_PACKAGE.getBytes()); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo( + TEST_EXCLUSIVE_MANAGER_PACKAGE, 0); + + assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, + mBluetoothDevice)).isEqualTo(true); + } + @Test + public void isExclusivelyManaged_hasComponentName_packageInstalledAndEnabled_returnTrue() + throws Exception { when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)).thenReturn( - exclusiveManagerName.getBytes()); + TEST_EXCLUSIVE_MANAGER_COMPONENT.getBytes()); when(mContext.getPackageManager()).thenReturn(mPackageManager); - doReturn(new PackageInfo()).when(mPackageManager).getPackageInfo(exclusiveManagerName, 0); + doReturn(new ApplicationInfo()).when(mPackageManager).getApplicationInfo( + TEST_EXCLUSIVE_MANAGER_PACKAGE, 0); assertThat(BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext, - mBluetoothDevice)).isEqualTo(true); + mBluetoothDevice)).isEqualTo(true); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java index b4bd4826ea08..b9bf9caddac7 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java @@ -753,33 +753,71 @@ public class CachedBluetoothDeviceTest { } @Test - public void getConnectionSummary_testHearingAidBatteryWithoutInCall_returnActiveBattery() { + public void getConnectionSummary_testHearingAidLeftEarBatteryNotInCall_returnActiveBattery() { // Arrange: - // 1. Profile: {HEARING_AID, Connected, Active} + // 1. Profile: {HEARING_AID, Connected, Active, Left ear} // 2. Battery Level: 10 // 3. Audio Manager: Normal (Without In Call) updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo()); mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); mBatteryLevel = 10; // Act & Assert: - // Get "Active, 10% battery" result with Battery Level 10. - assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, 10% battery"); + // Get "Active. L: 10% battery." result with Battery Level 10. + assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active. L: 10% battery."); } @Test - public void getTvConnectionSummary_testHearingAidBatteryWithoutInCall_returnBattery() { + public void getTvConnectionSummary_testHearingAidLeftEarBatteryWithoutInCall_returnBattery() { // Arrange: - // 1. Profile: {HEARING_AID, Connected, Active} + // 1. Profile: {HEARING_AID, Connected, Active, Left ear} // 2. Battery Level: 10 // 3. Audio Manager: Normal (Without In Call) updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo()); mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); mBatteryLevel = 10; // Act & Assert: - // Get "Active, 10% battery" result with Battery Level 10. - assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo("Battery 10%"); + // Get "Left: 10% battery" result with Battery Level 10. + assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo( + "Left: 10% battery"); + } + + @Test + public void getConnectionSummary_testHearingAidLeftEarBatteryInCall_returnActiveBattery() { + // Arrange: + // 1. Profile: {HEARING_AID, Connected, Active, Left ear} + // 2. Battery Level: 10 + // 3. Audio Manager: In Call + updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo()); + mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); + mAudioManager.setMode(AudioManager.MODE_IN_CALL); + mBatteryLevel = 10; + + // Act & Assert: + // Get "Active. L: 10% battery." result with Battery Level 10. + assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active. L: 10% battery."); + } + + @Test + public void getTvConnectionSummary_testHearingAidLeftEarBatteryInCall_returnBattery() { + // Arrange: + // 1. Profile: {HEARING_AID, Connected, Active, Left ear} + // 2. Battery Level: 10 + // 3. Audio Manager: In Call + updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo()); + mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); + mAudioManager.setMode(AudioManager.MODE_IN_CALL); + mBatteryLevel = 10; + + // Act & Assert: + // Get "Left: 10% battery" result with Battery Level 10. + assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo( + "Left: 10% battery"); } @Test @@ -851,35 +889,45 @@ public class CachedBluetoothDeviceTest { } @Test - public void getConnectionSummary_testHearingAidBatteryInCall_returnActiveBattery() { + public void getConnectionSummary_testHearingAidBothEarBattery_returnActiveBattery() { // Arrange: - // 1. Profile: {HEARING_AID, Connected, Active} + // 1. Profile: {HEARING_AID, Connected, Active, Both ear} // 2. Battery Level: 10 // 3. Audio Manager: In Call + mCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo()); updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mSubCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo()); + updateSubDeviceProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mCachedDevice.setSubDevice(mSubCachedDevice); mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); mAudioManager.setMode(AudioManager.MODE_IN_CALL); mBatteryLevel = 10; // Act & Assert: - // Get "Active, 10% battery" result with Battery Level 10. - assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, 10% battery"); + // Get "Active. L: 10%, R: 10% battery." result with Battery Level 10. + assertThat(mCachedDevice.getConnectionSummary().toString()) + .isEqualTo("Active. L: 10%, R: 10% battery."); } @Test - public void getTvConnectionSummary_testHearingAidBatteryInCall_returnBattery() { + public void getTvConnectionSummary_testHearingAidBothEarBattery_returnActiveBattery() { // Arrange: - // 1. Profile: {HEARING_AID, Connected, Active} + // 1. Profile: {HEARING_AID, Connected, Active, Both ear} // 2. Battery Level: 10 // 3. Audio Manager: In Call + mCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo()); updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mSubCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo()); + updateSubDeviceProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); + mCachedDevice.setSubDevice(mSubCachedDevice); mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); mAudioManager.setMode(AudioManager.MODE_IN_CALL); mBatteryLevel = 10; // Act & Assert: - // Get "Active, 10% battery" result with Battery Level 10. - assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo("Battery 10%"); + // Get "Left: 10% battery Right: 10% battery" result with Battery Level 10. + assertThat(mCachedDevice.getTvConnectionSummary().toString()) + .isEqualTo("Left: 10% battery Right: 10% battery"); } @Test @@ -1151,7 +1199,7 @@ public class CachedBluetoothDeviceTest { updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED); updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); - mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); + mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP); when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn( "true".getBytes()); when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn( @@ -1160,7 +1208,7 @@ public class CachedBluetoothDeviceTest { TWS_BATTERY_RIGHT.getBytes()); assertThat(mCachedDevice.getConnectionSummary()).isEqualTo( - "Active, L: 15% battery, R: 25% battery"); + "Active. L: 15%, R: 25% battery."); } @Test @@ -1169,7 +1217,7 @@ public class CachedBluetoothDeviceTest { updateProfileStatus(mHfpProfile, BluetoothProfile.STATE_CONNECTED); updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED); when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); - mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEARING_AID); + mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP); when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn( "true".getBytes()); when(mDevice.getMetadata(BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY)).thenReturn( @@ -1178,7 +1226,7 @@ public class CachedBluetoothDeviceTest { TWS_BATTERY_RIGHT.getBytes()); assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo( - "Left 15% Right 25%"); + "Left: 15% battery Right: 25% battery"); } @Test @@ -1741,16 +1789,6 @@ public class CachedBluetoothDeviceTest { BluetoothProfile.STATE_CONNECTED); } - private void updateProfileStatus(LocalBluetoothProfile profile, int status) { - doReturn(status).when(profile).getConnectionStatus(mDevice); - mCachedDevice.onProfileStateChanged(profile, status); - } - - private void updateSubDeviceProfileStatus(LocalBluetoothProfile profile, int status) { - doReturn(status).when(profile).getConnectionStatus(mSubDevice); - mSubCachedDevice.onProfileStateChanged(profile, status); - } - @Test public void getSubDevice_setSubDevice() { mCachedDevice.setSubDevice(mSubCachedDevice); @@ -2030,6 +2068,29 @@ public class CachedBluetoothDeviceTest { assertThat(mCachedDevice.getConnectionSummary(false)).isNull(); } + private void updateProfileStatus(LocalBluetoothProfile profile, int status) { + doReturn(status).when(profile).getConnectionStatus(mDevice); + mCachedDevice.onProfileStateChanged(profile, status); + updateConnectionStatus(mCachedDevice); + } + + private void updateSubDeviceProfileStatus(LocalBluetoothProfile profile, int status) { + doReturn(status).when(profile).getConnectionStatus(mSubDevice); + mSubCachedDevice.onProfileStateChanged(profile, status); + updateConnectionStatus(mSubCachedDevice); + } + + private void updateConnectionStatus(CachedBluetoothDevice cachedBluetoothDevice) { + for (LocalBluetoothProfile profile : cachedBluetoothDevice.getProfiles()) { + int status = cachedBluetoothDevice.getProfileConnectionState(profile); + if (status == BluetoothProfile.STATE_CONNECTED) { + when(cachedBluetoothDevice.getDevice().isConnected()).thenReturn(true); + return; + } + } + when(cachedBluetoothDevice.getDevice().isConnected()).thenReturn(false); + } + private HearingAidInfo getLeftAshaHearingAidInfo() { return new HearingAidInfo.Builder() .setAshaDeviceSide(HearingAidProfile.DeviceSide.SIDE_LEFT) diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 461b6b3ac09b..70ce202c289a 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -2949,9 +2949,6 @@ class SettingsProtoDumpUtil { dumpSetting(s, p, Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, SystemSettingsProto.Screen.AUTO_BRIGHTNESS_ADJ); - dumpSetting(s, p, - Settings.System.SCREEN_BRIGHTNESS_FLOAT, - SystemSettingsProto.Screen.BRIGHTNESS_FLOAT); p.end(screenToken); dumpSetting(s, p, diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index c891dfc89c28..92167ee4d423 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -935,7 +935,6 @@ public class SettingsBackupTest { Settings.System.VOLUME_VOICE, // deprecated since API 2? Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug? Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only - Settings.System.SCREEN_BRIGHTNESS_FLOAT, Settings.System.SCREEN_BRIGHTNESS_FOR_ALS, Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE, Settings.System.WEAR_TTS_PREWARM_ENABLED, diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index fce7a00f9744..c04ec4f61c89 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -569,7 +569,7 @@ android_library { "androidx.test.uiautomator_uiautomator", "androidx.core_core-animation-testing", "mockito-target-extended-minus-junit4", - "mockito-kotlin2", + "mockito-kotlin-nodeps", "androidx.test.ext.junit", "androidx.test.ext.truth", "kotlin-test", @@ -656,6 +656,7 @@ java_library { "androidx.core_core-animation-testing", "androidx.test.ext.junit", "inline-mockito-robolectric-prebuilt", + "mockito-kotlin-nodeps", "platform-parametric-runner-lib", "SystemUICustomizationTestUtils", "kotlin-test", @@ -740,6 +741,7 @@ android_ravenwood_test { "androidx.core_core-animation-testing", "androidx.test.ext.junit", "kosmos", + "mockito-kotlin-nodeps", ], libs: [ "android.test.runner", diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig index 14ebc3907c04..755fe2a4476a 100644 --- a/packages/SystemUI/aconfig/accessibility.aconfig +++ b/packages/SystemUI/aconfig/accessibility.aconfig @@ -4,6 +4,16 @@ container: "system" # NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors. flag { + name: "delay_show_magnification_button" + namespace: "accessibility" + description: "Delays the showing of magnification mode switch button." + bug: "338259519" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "floating_menu_animated_tuck" namespace: "accessibility" description: "Sets up animations for tucking/untucking and adjusts clipbounds." diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 21881f657e6d..0dd956c7a3ae 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -550,6 +550,13 @@ flag { } flag { + name: "enable_contextual_tip_for_mute_volume" + namespace: "systemui" + description: "Enables the contextual tip for muting the volume." + bug: "337737048" +} + +flag { name: "disable_contextual_tips_frequency_check" description: "Disables frequency capping check for contextual tips." namespace: "systemui" @@ -850,6 +857,16 @@ flag { } flag { + name: "restart_dream_on_unocclude" + namespace: "systemui" + description: "re-enters dreaming upon unocclude when dreaming when originally occluding" + bug: "338051457" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "communal_bouncer_do_not_modify_plugin_open" namespace: "systemui" description: "do not modify notification shade when handling bouncer expansion." @@ -858,3 +875,30 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "app_clips_backlinks" + namespace: "systemui" + description: "Enables Backlinks improvement feature in App Clips" + bug: "300307759" +} + +flag { + name: "qs_custom_tile_click_guaranteed_bug_fix" + namespace: "systemui" + description: "Guarantee that clicks on a tile always happen by postponing onStopListening until after the click." + bug: "339290820" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "media_controls_user_initiated_dismiss" + namespace: "systemui" + description: "Only dismiss media notifications when the control was removed by the user." + bug: "335875159" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt index 1e60b984991a..23df26fdb246 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt @@ -23,6 +23,7 @@ import android.app.TaskInfo import android.graphics.Matrix import android.graphics.Rect import android.graphics.RectF +import android.os.Binder import android.os.Build import android.os.Handler import android.os.Looper @@ -36,7 +37,11 @@ import android.view.SyncRtSurfaceTransactionApplier import android.view.View import android.view.ViewGroup import android.view.WindowManager +import android.view.WindowManager.TRANSIT_CLOSE +import android.view.WindowManager.TRANSIT_TO_BACK import android.view.animation.PathInterpolator +import android.window.RemoteTransition +import android.window.TransitionFilter import androidx.annotation.AnyThread import androidx.annotation.BinderThread import androidx.annotation.UiThread @@ -44,6 +49,10 @@ import com.android.app.animation.Interpolators import com.android.internal.annotations.VisibleForTesting import com.android.internal.policy.ScreenDecorationsUtils import com.android.systemui.Flags.activityTransitionUseLargestWindow +import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary +import com.android.wm.shell.shared.IShellTransitions +import com.android.wm.shell.shared.ShellTransitions +import java.util.concurrent.Executor import kotlin.math.roundToInt private const val TAG = "ActivityTransitionAnimator" @@ -52,14 +61,22 @@ private const val TAG = "ActivityTransitionAnimator" * A class that allows activities to be started in a seamless way from a view that is transforming * nicely into the starting window. */ -class ActivityTransitionAnimator( +class ActivityTransitionAnimator +@JvmOverloads +constructor( + /** The executor that runs on the main thread. */ + private val mainExecutor: Executor, + + /** The object used to register ephemeral returns and long-lived transitions. */ + private val transitionRegister: TransitionRegister? = null, + /** The animator used when animating a View into an app. */ - private val transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR, + private val transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor), /** The animator used when animating a Dialog into an app. */ // TODO(b/218989950): Remove this animator and instead set the duration of the dim fade out to // TIMINGS.contentBeforeFadeOutDuration. - private val dialogToAppAnimator: TransitionAnimator = DEFAULT_DIALOG_TO_APP_ANIMATOR, + private val dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor), /** * Whether we should disable the WindowManager timeout. This should be set to true in tests @@ -68,6 +85,36 @@ class ActivityTransitionAnimator( // TODO(b/301385865): Remove this flag. private val disableWmTimeout: Boolean = false, ) { + @JvmOverloads + constructor( + mainExecutor: Executor, + shellTransitions: ShellTransitions, + transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor), + dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor), + disableWmTimeout: Boolean = false, + ) : this( + mainExecutor, + TransitionRegister.fromShellTransitions(shellTransitions), + transitionAnimator, + dialogToAppAnimator, + disableWmTimeout, + ) + + @JvmOverloads + constructor( + mainExecutor: Executor, + iShellTransitions: IShellTransitions, + transitionAnimator: TransitionAnimator = defaultTransitionAnimator(mainExecutor), + dialogToAppAnimator: TransitionAnimator = defaultDialogToAppAnimator(mainExecutor), + disableWmTimeout: Boolean = false, + ) : this( + mainExecutor, + TransitionRegister.fromIShellTransitions(iShellTransitions), + transitionAnimator, + dialogToAppAnimator, + disableWmTimeout, + ) + companion object { /** The timings when animating a View into an app. */ @JvmField @@ -100,10 +147,6 @@ class ActivityTransitionAnimator( // TODO(b/288507023): Remove this flag. @JvmField val DEBUG_TRANSITION_ANIMATION = Build.IS_DEBUGGABLE - private val DEFAULT_TRANSITION_ANIMATOR = TransitionAnimator(TIMINGS, INTERPOLATORS) - private val DEFAULT_DIALOG_TO_APP_ANIMATOR = - TransitionAnimator(DIALOG_TIMINGS, INTERPOLATORS) - /** Durations & interpolators for the navigation bar fading in & out. */ private const val ANIMATION_DURATION_NAV_FADE_IN = 266L private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L @@ -121,6 +164,14 @@ class ActivityTransitionAnimator( * cancelled by WM. */ private const val LONG_TRANSITION_TIMEOUT = 5_000L + + private fun defaultTransitionAnimator(mainExecutor: Executor): TransitionAnimator { + return TransitionAnimator(mainExecutor, TIMINGS, INTERPOLATORS) + } + + private fun defaultDialogToAppAnimator(mainExecutor: Executor): TransitionAnimator { + return TransitionAnimator(mainExecutor, DIALOG_TIMINGS, INTERPOLATORS) + } } /** @@ -223,6 +274,10 @@ class ActivityTransitionAnimator( } } + if (animationAdapter != null && controller.transitionCookie != null) { + registerEphemeralReturnAnimation(controller, transitionRegister) + } + val launchResult = intentStarter(animationAdapter) // Only animate if the app is not already on top and will be opened, unless we are on the @@ -257,9 +312,7 @@ class ActivityTransitionAnimator( private fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) { if (Looper.myLooper() != Looper.getMainLooper()) { - this.transitionContainer.context.mainExecutor.execute { - callOnIntentStartedOnMainThread(willAnimate) - } + mainExecutor.execute { callOnIntentStartedOnMainThread(willAnimate) } } else { if (DEBUG_TRANSITION_ANIMATION) { Log.d( @@ -294,6 +347,66 @@ class ActivityTransitionAnimator( } } + /** + * Uses [transitionRegister] to set up the return animation for the given [launchController]. + * + * De-registration is set up automatically once the return animation is run. + * + * TODO(b/339194555): automatically de-register when the launchable is detached. + */ + private fun registerEphemeralReturnAnimation( + launchController: Controller, + transitionRegister: TransitionRegister? + ) { + if (!returnAnimationFrameworkLibrary()) return + + var cleanUpRunnable: Runnable? = null + val returnRunner = + createRunner( + object : DelegateTransitionAnimatorController(launchController) { + override val isLaunching = false + + override fun onTransitionAnimationCancelled( + newKeyguardOccludedState: Boolean? + ) { + super.onTransitionAnimationCancelled(newKeyguardOccludedState) + cleanUp() + } + + override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { + super.onTransitionAnimationEnd(isExpandingFullyAbove) + cleanUp() + } + + private fun cleanUp() { + cleanUpRunnable?.run() + } + } + ) + + // mTypeSet and mModes match back signals only, and not home. This is on purpose, because + // we only want ephemeral return animations triggered in these scenarios. + val filter = + TransitionFilter().apply { + mTypeSet = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK) + mRequirements = + arrayOf( + TransitionFilter.Requirement().apply { + mLaunchCookie = launchController.transitionCookie + mModes = intArrayOf(TRANSIT_CLOSE, TRANSIT_TO_BACK) + } + ) + } + val transition = + RemoteTransition( + RemoteAnimationRunnerCompat.wrap(returnRunner), + "${launchController.transitionCookie}_returnTransition" + ) + + transitionRegister?.register(filter, transition) + cleanUpRunnable = Runnable { transitionRegister?.unregister(transition) } + } + /** Add a [Listener] that can listen to transition animations. */ fun addListener(listener: Listener) { listeners.add(listener) @@ -378,8 +491,14 @@ class ActivityTransitionAnimator( * Note: The background of [view] should be a (rounded) rectangle so that it can be * properly animated. */ + @JvmOverloads @JvmStatic - fun fromView(view: View, cujType: Int? = null): Controller? { + fun fromView( + view: View, + cujType: Int? = null, + cookie: TransitionCookie? = null, + returnCujType: Int? = null + ): Controller? { // Make sure the View we launch from implements LaunchableView to avoid visibility // issues. if (view !is LaunchableView) { @@ -400,7 +519,7 @@ class ActivityTransitionAnimator( return null } - return GhostedViewTransitionAnimatorController(view, cujType) + return GhostedViewTransitionAnimatorController(view, cujType, cookie, returnCujType) } } @@ -424,6 +543,17 @@ class ActivityTransitionAnimator( get() = false /** + * The cookie associated with the transition controlled by this [Controller]. + * + * This should be defined for all return [Controller] (when [isLaunching] is false) and for + * their associated launch [Controller]s. + * + * For the recommended format, see [TransitionCookie]. + */ + val transitionCookie: TransitionCookie? + get() = null + + /** * The intent was started. If [willAnimate] is false, nothing else will happen and the * animation will not be started. */ @@ -479,12 +609,10 @@ class ActivityTransitionAnimator( controller: Controller, callback: Callback, /** The animator to use to animate the window transition. */ - transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR, + transitionAnimator: TransitionAnimator, /** Listener for animation lifecycle events. */ listener: Listener? = null ) : IRemoteAnimationRunner.Stub() { - private val context = controller.transitionContainer.context - // This is being passed across IPC boundaries and cycles (through PendingIntentRecords, // etc.) are possible. So we need to make sure we drop any references that might // transitively cause leaks when we're done with animation. @@ -493,11 +621,12 @@ class ActivityTransitionAnimator( init { delegate = AnimationDelegate( + mainExecutor, controller, callback, DelegatingAnimationCompletionListener(listener, this::dispose), transitionAnimator, - disableWmTimeout + disableWmTimeout, ) } @@ -510,7 +639,7 @@ class ActivityTransitionAnimator( finishedCallback: IRemoteAnimationFinishedCallback? ) { val delegate = delegate - context.mainExecutor.execute { + mainExecutor.execute { if (delegate == null) { Log.i(TAG, "onAnimationStart called after completion") // Animation started too late and timed out already. We need to still @@ -525,7 +654,7 @@ class ActivityTransitionAnimator( @BinderThread override fun onAnimationCancelled() { val delegate = delegate - context.mainExecutor.execute { + mainExecutor.execute { delegate ?: Log.wtf(TAG, "onAnimationCancelled called after completion") delegate?.onAnimationCancelled() } @@ -535,19 +664,21 @@ class ActivityTransitionAnimator( fun dispose() { // Drop references to animation controller once we're done with the animation // to avoid leaking. - context.mainExecutor.execute { delegate = null } + mainExecutor.execute { delegate = null } } } class AnimationDelegate @JvmOverloads constructor( + private val mainExecutor: Executor, private val controller: Controller, private val callback: Callback, /** Listener for animation lifecycle events. */ private val listener: Listener? = null, /** The animator to use to animate the window transition. */ - private val transitionAnimator: TransitionAnimator = DEFAULT_TRANSITION_ANIMATOR, + private val transitionAnimator: TransitionAnimator = + defaultTransitionAnimator(mainExecutor), /** * Whether we should disable the WindowManager timeout. This should be set to true in tests @@ -643,7 +774,7 @@ class ActivityTransitionAnimator( return } - val window = findRootTaskIfPossible(apps) + val window = findTargetWindowIfPossible(apps) if (window == null) { Log.i(TAG, "Aborting the animation as no window is opening") callback?.invoke() @@ -667,7 +798,7 @@ class ActivityTransitionAnimator( startAnimation(window, navigationBar, callback) } - private fun findRootTaskIfPossible( + private fun findTargetWindowIfPossible( apps: Array<out RemoteAnimationTarget>? ): RemoteAnimationTarget? { if (apps == null) { @@ -685,6 +816,19 @@ class ActivityTransitionAnimator( for (it in apps) { if (it.mode == targetMode) { if (activityTransitionUseLargestWindow()) { + if (returnAnimationFrameworkLibrary()) { + // If the controller contains a cookie, _only_ match if the candidate + // contains the matching cookie. + if ( + controller.transitionCookie != null && + it.taskInfo + ?.launchCookies + ?.contains(controller.transitionCookie) != true + ) { + continue + } + } + if ( candidate == null || !it.hasAnimatingParent && candidate.hasAnimatingParent @@ -797,11 +941,7 @@ class ActivityTransitionAnimator( progress: Float, linearProgress: Float ) { - // Apply the state to the window only if it is visible, i.e. when the - // expanding view is *not* visible. - if (!state.visible) { - applyStateToWindow(window, state, linearProgress) - } + applyStateToWindow(window, state, linearProgress) navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) } listener?.onTransitionAnimationProgress(linearProgress) @@ -1039,4 +1179,72 @@ class ActivityTransitionAnimator( return (this.width() * this.height()) > (other.width() * other.height()) } } + + /** + * Wraps one of the two methods we have to register remote transitions with WM Shell: + * - for in-process registrations (e.g. System UI) we use [ShellTransitions] + * - for cross-process registrations (e.g. Launcher) we use [IShellTransitions] + * + * Important: each instance of this class must wrap exactly one of the two. + */ + class TransitionRegister + private constructor( + private val shellTransitions: ShellTransitions? = null, + private val iShellTransitions: IShellTransitions? = null, + ) { + init { + assert((shellTransitions != null).xor(iShellTransitions != null)) + } + + companion object { + /** Provides a [TransitionRegister] instance wrapping [ShellTransitions]. */ + fun fromShellTransitions(shellTransitions: ShellTransitions): TransitionRegister { + return TransitionRegister(shellTransitions = shellTransitions) + } + + /** Provides a [TransitionRegister] instance wrapping [IShellTransitions]. */ + fun fromIShellTransitions(iShellTransitions: IShellTransitions): TransitionRegister { + return TransitionRegister(iShellTransitions = iShellTransitions) + } + } + + /** Register [remoteTransition] with WM Shell using the given [filter]. */ + internal fun register( + filter: TransitionFilter, + remoteTransition: RemoteTransition, + ) { + shellTransitions?.registerRemote(filter, remoteTransition) + iShellTransitions?.registerRemote(filter, remoteTransition) + } + + /** Unregister [remoteTransition] from WM Shell. */ + internal fun unregister(remoteTransition: RemoteTransition) { + shellTransitions?.unregisterRemote(remoteTransition) + iShellTransitions?.unregisterRemote(remoteTransition) + } + } + + /** + * A cookie used to uniquely identify a task launched using an + * [ActivityTransitionAnimator.Controller]. + * + * The [String] encapsulated by this class should be formatted in such a way to be unique across + * the system, but reliably constant for the same associated launchable. + * + * Recommended naming scheme: + * - DO use the fully qualified name of the class that owns the instance of the launchable, + * along with a concise and precise description of the purpose of the launchable in question. + * - DO NOT introduce uniqueness through the use of timestamps or other runtime variables that + * will change if the instance is destroyed and re-created. + * + * Example: "com.not.the.real.class.name.ShadeController_openSettingsButton" + * + * Note that sometimes (e.g. in recycler views) there could be multiple instances of the same + * launchable, and no static knowledge to adequately differentiate between them using a single + * description. In this case, the recommendation is to append a unique identifier related to the + * contents of the launchable. + * + * Example: “com.not.the.real.class.name.ToastWebResult_launchAga_id143256” + */ + data class TransitionCookie(private val cookie: String) : Binder() } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt index b89ebfcb3675..f5d01d70e077 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt @@ -37,6 +37,7 @@ import com.android.internal.jank.Cuj.CujType import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.util.maybeForceFullscreen import com.android.systemui.util.registerAnimationOnBackInvoked +import java.util.concurrent.Executor import kotlin.math.roundToInt private const val TAG = "DialogTransitionAnimator" @@ -55,10 +56,16 @@ private const val TAG = "DialogTransitionAnimator" class DialogTransitionAnimator @JvmOverloads constructor( + private val mainExecutor: Executor, private val callback: Callback, private val interactionJankMonitor: InteractionJankMonitor, private val featureFlags: AnimationFeatureFlags, - private val transitionAnimator: TransitionAnimator = TransitionAnimator(TIMINGS, INTERPOLATORS), + private val transitionAnimator: TransitionAnimator = + TransitionAnimator( + mainExecutor, + TIMINGS, + INTERPOLATORS, + ), private val isForTesting: Boolean = false, ) { private companion object { @@ -937,24 +944,9 @@ private class AnimatedDialog( } override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) { - // onLaunchAnimationEnd is called by an Animator at the end of the animation, - // on a Choreographer animation tick. The following calls will move the animated - // content from the dialog overlay back to its original position, and this - // change must be reflected in the next frame given that we then sync the next - // frame of both the content and dialog ViewRoots. However, in case that content - // is rendered by Compose, whose compositions are also scheduled on a - // Choreographer frame, any state change made *right now* won't be reflected in - // the next frame given that a Choreographer frame can't schedule another and - // have it happen in the same frame. So we post the forwarded calls to - // [Controller.onLaunchAnimationEnd], leaving this Choreographer frame, ensuring - // that the move of the content back to its original window will be reflected in - // the next frame right after [onLaunchAnimationEnd] is called. - dialog.context.mainExecutor.execute { - startController.onTransitionAnimationEnd(isExpandingFullyAbove) - endController.onTransitionAnimationEnd(isExpandingFullyAbove) - - onLaunchAnimationEnd() - } + startController.onTransitionAnimationEnd(isExpandingFullyAbove) + endController.onTransitionAnimationEnd(isExpandingFullyAbove) + onLaunchAnimationEnd() } override fun onTransitionAnimationProgress( diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt index e4bb2adbefb4..21557b8bb402 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/Expandable.kt @@ -25,10 +25,30 @@ interface Expandable { * [Expandable] into an Activity, or return `null` if this [Expandable] should not be animated * (e.g. if it is currently not attached or visible). * - * @param cujType the CUJ type from the [com.android.internal.jank.InteractionJankMonitor] + * @param launchCujType The CUJ type from the [com.android.internal.jank.InteractionJankMonitor] * associated to the launch that will use this controller. + * @param cookie The unique cookie associated with the launch that will use this controller. + * This is required iff the a return animation should be included. + * @param returnCujType The CUJ type from the [com.android.internal.jank.InteractionJankMonitor] + * associated to the return animation that will use this controller. */ - fun activityTransitionController(cujType: Int? = null): ActivityTransitionAnimator.Controller? + fun activityTransitionController( + launchCujType: Int? = null, + cookie: ActivityTransitionAnimator.TransitionCookie? = null, + returnCujType: Int? = null + ): ActivityTransitionAnimator.Controller? + + /** + * See [activityTransitionController] above. + * + * Interfaces don't support [JvmOverloads], so this is a useful overload for Java usages that + * don't use the return-related parameters. + */ + fun activityTransitionController( + launchCujType: Int? = null + ): ActivityTransitionAnimator.Controller? { + return activityTransitionController(launchCujType, cookie = null, returnCujType = null) + } /** * Create a [DialogTransitionAnimator.Controller] that can be used to expand this [Expandable] @@ -48,9 +68,16 @@ interface Expandable { fun fromView(view: View): Expandable { return object : Expandable { override fun activityTransitionController( - cujType: Int?, + launchCujType: Int?, + cookie: ActivityTransitionAnimator.TransitionCookie?, + returnCujType: Int? ): ActivityTransitionAnimator.Controller? { - return ActivityTransitionAnimator.Controller.fromView(view, cujType) + return ActivityTransitionAnimator.Controller.fromView( + view, + launchCujType, + cookie, + returnCujType + ) } override fun dialogTransitionController( diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt index fd79f62debce..9d4507337e51 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt @@ -59,8 +59,12 @@ constructor( /** The view that will be ghosted and from which the background will be extracted. */ private val ghostedView: View, - /** The [CujType] associated to this animation. */ - private val cujType: Int? = null, + /** The [CujType] associated to this launch animation. */ + private val launchCujType: Int? = null, + override val transitionCookie: ActivityTransitionAnimator.TransitionCookie? = null, + + /** The [CujType] associated to this return animation. */ + private val returnCujType: Int? = null, private var interactionJankMonitor: InteractionJankMonitor = InteractionJankMonitor.getInstance(), ) : ActivityTransitionAnimator.Controller { @@ -104,6 +108,15 @@ constructor( */ private val background: Drawable? + /** CUJ identifier accounting for whether this controller is for a launch or a return. */ + private val cujType: Int? + get() = + if (isLaunching) { + launchCujType + } else { + returnCujType + } + init { // Make sure the View we launch from implements LaunchableView to avoid visibility issues. if (ghostedView !is LaunchableView) { diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt index 679c96909e59..cc55df129c33 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt @@ -31,12 +31,17 @@ import android.view.animation.Interpolator import androidx.annotation.VisibleForTesting import com.android.app.animation.Interpolators.LINEAR import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary +import java.util.concurrent.Executor import kotlin.math.roundToInt private const val TAG = "TransitionAnimator" /** A base class to animate a window (activity or dialog) launch to or return from a view . */ -class TransitionAnimator(private val timings: Timings, private val interpolators: Interpolators) { +class TransitionAnimator( + private val mainExecutor: Executor, + private val timings: Timings, + private val interpolators: Interpolators, +) { companion object { internal const val DEBUG = false private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC) @@ -351,11 +356,27 @@ class TransitionAnimator(private val timings: Timings, private val interpolators if (DEBUG) { Log.d(TAG, "Animation ended") } - controller.onTransitionAnimationEnd(isExpandingFullyAbove) - transitionContainerOverlay.remove(windowBackgroundLayer) - if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) { - openingWindowSyncViewOverlay?.remove(windowBackgroundLayer) + // onAnimationEnd is called at the end of the animation, on a Choreographer + // animation tick. During dialog launches, the following calls will move the + // animated content from the dialog overlay back to its original position, and + // this change must be reflected in the next frame given that we then sync the + // next frame of both the content and dialog ViewRoots. During SysUI activity + // launches, we will instantly collapse the shade at the end of the transition. + // However, if those are rendered by Compose, whose compositions are also + // scheduled on a Choreographer frame, any state change made *right now* won't + // be reflected in the next frame given that a Choreographer frame can't + // schedule another and have it happen in the same frame. So we post the + // forwarded calls to [Controller.onLaunchAnimationEnd] in the main executor, + // leaving this Choreographer frame, ensuring that any state change applied by + // onTransitionAnimationEnd() will be reflected in the same frame. + mainExecutor.execute { + controller.onTransitionAnimationEnd(isExpandingFullyAbove) + transitionContainerOverlay.remove(windowBackgroundLayer) + + if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) { + openingWindowSyncViewOverlay?.remove(windowBackgroundLayer) + } } } } diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt index c7f0a965206e..17a606171a9e 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt @@ -134,13 +134,15 @@ internal class ExpandableControllerImpl( override val expandable: Expandable = object : Expandable { override fun activityTransitionController( - cujType: Int?, + launchCujType: Int?, + cookie: ActivityTransitionAnimator.TransitionCookie?, + returnCujType: Int? ): ActivityTransitionAnimator.Controller? { if (!isComposed.value) { return null } - return activityController(cujType) + return activityController(launchCujType, cookie, returnCujType) } override fun dialogTransitionController( @@ -262,10 +264,27 @@ internal class ExpandableControllerImpl( } /** Create an [ActivityTransitionAnimator.Controller] that can be used to animate activities. */ - private fun activityController(cujType: Int?): ActivityTransitionAnimator.Controller { + private fun activityController( + launchCujType: Int?, + cookie: ActivityTransitionAnimator.TransitionCookie?, + returnCujType: Int? + ): ActivityTransitionAnimator.Controller { val delegate = transitionController() return object : ActivityTransitionAnimator.Controller, TransitionAnimator.Controller by delegate { + /** + * CUJ identifier accounting for whether this controller is for a launch or a return. + */ + private val cujType: Int? + get() = + if (isLaunching) { + launchCujType + } else { + returnCujType + } + + override val transitionCookie = cookie + override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) { delegate.onTransitionAnimationStart(isExpandingFullyAbove) overlay.value = composeViewRoot.rootView.overlay as ViewGroupOverlay diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutout.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutout.kt index 3eb1b14e72ba..604b517e19e5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutout.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/DisplayCutout.kt @@ -35,6 +35,7 @@ data class DisplayCutout( val viewDisplayCutoutKeyguardStatusBarView: ViewDisplayCutout? = null, ) { fun width() = abs(right.value - left.value).dp + fun height() = abs(bottom.value - top.value).dp } enum class CutoutLocation { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt index ec3c0030c44f..08e452cd0bd1 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.dimensionResource import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.ElementKey +import com.android.compose.animation.scene.ElementMatcher import com.android.compose.animation.scene.FixedSizeEdgeDetector import com.android.compose.animation.scene.LowestZIndexScenePicker import com.android.compose.animation.scene.MutableSceneTransitionLayoutState @@ -33,33 +34,38 @@ import com.android.systemui.communal.util.CommunalColors import com.android.systemui.res.R import com.android.systemui.scene.shared.model.SceneDataSourceDelegator import com.android.systemui.scene.ui.composable.SceneTransitionLayoutDataSource -import com.android.systemui.statusbar.phone.SystemUIDialogFactory object Communal { object Elements { val Scrim = ElementKey("Scrim", scenePicker = LowestZIndexScenePicker) - val Content = ElementKey("CommunalContent") + val Grid = ElementKey("CommunalContent") + val LockIcon = ElementKey("CommunalLockIcon") + val IndicationArea = ElementKey("CommunalIndicationArea") } } +object AllElements : ElementMatcher { + override fun matches(key: ElementKey, scene: SceneKey) = true +} + val sceneTransitions = transitions { to(CommunalScenes.Communal, key = CommunalTransitionKeys.SimpleFade) { spec = tween(durationMillis = 250) - fade(Communal.Elements.Scrim) - fade(Communal.Elements.Content) + fade(AllElements) } to(CommunalScenes.Communal) { spec = tween(durationMillis = 1000) - translate(Communal.Elements.Content, Edge.Right) - timestampRange(startMillis = 167, endMillis = 334) { - fade(Communal.Elements.Scrim) - fade(Communal.Elements.Content) - } + translate(Communal.Elements.Grid, Edge.Right) + timestampRange(startMillis = 167, endMillis = 334) { fade(AllElements) } } to(CommunalScenes.Blank) { spec = tween(durationMillis = 1000) - translate(Communal.Elements.Content, Edge.Right) - timestampRange(endMillis = 167) { fade(Communal.Elements.Content) } + translate(Communal.Elements.Grid, Edge.Right) + timestampRange(endMillis = 167) { + fade(Communal.Elements.Grid) + fade(Communal.Elements.IndicationArea) + fade(Communal.Elements.LockIcon) + } timestampRange(startMillis = 167, endMillis = 334) { fade(Communal.Elements.Scrim) } } } @@ -75,8 +81,8 @@ fun CommunalContainer( modifier: Modifier = Modifier, viewModel: CommunalViewModel, dataSourceDelegator: SceneDataSourceDelegator, - dialogFactory: SystemUIDialogFactory, colors: CommunalColors, + content: CommunalContent, ) { val coroutineScope = rememberCoroutineScope() val currentSceneKey: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank) @@ -127,7 +133,7 @@ fun CommunalContainer( userActions = mapOf(Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank) ) { - CommunalScene(viewModel, colors, dialogFactory, modifier = modifier) + CommunalScene(colors, content) } } @@ -139,20 +145,16 @@ fun CommunalContainer( /** Scene containing the glanceable hub UI. */ @Composable private fun SceneScope.CommunalScene( - viewModel: CommunalViewModel, colors: CommunalColors, - dialogFactory: SystemUIDialogFactory, + content: CommunalContent, modifier: Modifier = Modifier, ) { val backgroundColor by colors.backgroundColor.collectAsState() - Box( modifier = Modifier.element(Communal.Elements.Scrim) .fillMaxSize() .background(Color(backgroundColor.toArgb())), ) - Box(modifier.element(Communal.Elements.Content)) { - CommunalHub(viewModel = viewModel, dialogFactory = dialogFactory) - } + with(content) { Content(modifier = modifier) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt new file mode 100644 index 000000000000..776651558e48 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.ui.compose + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.unit.IntRect +import com.android.compose.animation.scene.SceneScope +import com.android.compose.theme.LocalAndroidColorScheme +import com.android.systemui.communal.ui.viewmodel.CommunalViewModel +import com.android.systemui.keyguard.ui.composable.blueprint.BlueprintAlignmentLines +import com.android.systemui.keyguard.ui.composable.section.LockSection +import com.android.systemui.statusbar.phone.SystemUIDialogFactory +import javax.inject.Inject + +/** Renders the content of the glanceable hub. */ +class CommunalContent +@Inject +constructor( + private val viewModel: CommunalViewModel, + private val dialogFactory: SystemUIDialogFactory, + private val lockSection: LockSection, +) { + + @Composable + fun SceneScope.Content(modifier: Modifier = Modifier) { + Layout( + modifier = modifier.fillMaxSize(), + content = { + CommunalHub( + viewModel = viewModel, + dialogFactory = dialogFactory, + modifier = Modifier.element(Communal.Elements.Grid) + ) + with(lockSection) { + LockIcon( + overrideColor = LocalAndroidColorScheme.current.onPrimaryContainer, + modifier = Modifier.element(Communal.Elements.LockIcon) + ) + } + } + ) { measurables, constraints -> + val communalGridMeasurable = measurables[0] + val lockIconMeasurable = measurables[1] + + val noMinConstraints = + constraints.copy( + minWidth = 0, + minHeight = 0, + ) + + val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints) + val lockIconBounds = + IntRect( + left = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Left], + top = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Top], + right = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Right], + bottom = lockIconPlaceable[BlueprintAlignmentLines.LockIcon.Bottom], + ) + + val communalGridPlaceable = + communalGridMeasurable.measure( + noMinConstraints.copy(maxHeight = lockIconBounds.top) + ) + + layout(constraints.maxWidth, constraints.maxHeight) { + communalGridPlaceable.place( + x = 0, + y = 0, + ) + lockIconPlaceable.place( + x = lockIconBounds.left, + y = lockIconBounds.top, + ) + } + } + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 79b57ca74f7d..02621f6c84f8 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -59,7 +59,8 @@ import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.outlined.Edit import androidx.compose.material.icons.outlined.TouchApp import androidx.compose.material.icons.outlined.Widgets @@ -107,7 +108,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.dimensionResource -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.CustomAccessibilityAction import androidx.compose.ui.semantics.contentDescription @@ -264,20 +264,8 @@ fun CommunalHub( } } - // TODO(b/326060686): Remove this once keyguard indication area can persist over hub - if (viewModel is CommunalViewModel) { - val isUnlocked by viewModel.deviceUnlocked.collectAsState(initial = false) - LockStateIcon( - modifier = - Modifier.align(Alignment.BottomCenter) - .padding(bottom = Dimensions.LockIconBottomPadding), - isUnlocked = isUnlocked, - ) - } - if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) { Toolbar( - isDraggingToRemove = isDraggingToRemove, setToolbarSize = { toolbarSize = it }, setRemoveButtonCoordinates = { removeButtonCoordinates = it }, onEditDone = onEditDone, @@ -549,26 +537,6 @@ private fun EmptyStateCta( } } -@Composable -private fun LockStateIcon( - isUnlocked: Boolean, - modifier: Modifier = Modifier, -) { - val colors = LocalAndroidColorScheme.current - val resource = - if (isUnlocked) { - R.drawable.ic_unlocked - } else { - R.drawable.ic_lock - } - Icon( - painter = painterResource(id = resource), - contentDescription = null, - tint = colors.onPrimaryContainer, - modifier = modifier.size(Dimensions.LockIconSize), - ) -} - /** * Toolbar that contains action buttons to * 1) open the widget picker @@ -577,7 +545,6 @@ private fun LockStateIcon( */ @Composable private fun Toolbar( - isDraggingToRemove: Boolean, removeEnabled: Boolean, onRemoveClicked: () -> Unit, setToolbarSize: (toolbarSize: IntSize) -> Unit, @@ -591,7 +558,7 @@ private fun Toolbar( label = "RemoveButtonAlphaAnimation" ) - Row( + Box( modifier = Modifier.fillMaxWidth() .padding( @@ -600,65 +567,54 @@ private fun Toolbar( end = Dimensions.ToolbarPaddingHorizontal, ) .onSizeChanged { setToolbarSize(it) }, - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically ) { val spacerModifier = Modifier.width(ButtonDefaults.IconSpacing) - Button( - onClick = onOpenWidgetPicker, - colors = filledButtonColors(), - contentPadding = Dimensions.ButtonPadding - ) { - Icon(Icons.Default.Add, stringResource(R.string.hub_mode_add_widget_button_text)) - Spacer(spacerModifier) - Text( - text = stringResource(R.string.hub_mode_add_widget_button_text), - ) - } - val colors = LocalAndroidColorScheme.current - if (isDraggingToRemove) { + if (!removeEnabled) { Button( - // Button is disabled to make it non-clickable - enabled = false, - onClick = {}, - colors = - ButtonDefaults.buttonColors( - disabledContainerColor = colors.primary, - disabledContentColor = colors.onPrimary, - ), - contentPadding = Dimensions.ButtonPadding, - modifier = Modifier.onGloballyPositioned { setRemoveButtonCoordinates(it) } + modifier = Modifier.align(Alignment.CenterStart), + onClick = onOpenWidgetPicker, + colors = filledButtonColors(), + contentPadding = Dimensions.ButtonPadding ) { - RemoveButtonContent(spacerModifier) + Icon(Icons.Default.Add, stringResource(R.string.hub_mode_add_widget_button_text)) + Spacer(spacerModifier) + Text( + text = stringResource(R.string.hub_mode_add_widget_button_text), + ) } - } else { - OutlinedButton( - enabled = removeEnabled, + } + + if (removeEnabled) { + Button( onClick = onRemoveClicked, - colors = - ButtonDefaults.outlinedButtonColors( - contentColor = colors.primary, - disabledContentColor = colors.primary - ), - border = BorderStroke(width = 1.0.dp, color = colors.primary), + colors = filledButtonColors(), contentPadding = Dimensions.ButtonPadding, modifier = Modifier.graphicsLayer { alpha = removeButtonAlpha } .onGloballyPositioned { setRemoveButtonCoordinates(it) } + .align(Alignment.Center) ) { RemoveButtonContent(spacerModifier) } } - Button( - onClick = onEditDone, - colors = filledButtonColors(), - contentPadding = Dimensions.ButtonPadding - ) { - Text( - text = stringResource(R.string.hub_mode_editing_exit_button_text), - ) + if (!removeEnabled) { + Button( + modifier = Modifier.align(Alignment.CenterEnd), + onClick = onEditDone, + colors = filledButtonColors(), + contentPadding = Dimensions.ButtonPadding + ) { + Icon( + Icons.Default.Check, + stringResource(id = R.string.hub_mode_editing_exit_button_text) + ) + Spacer(spacerModifier) + Text( + text = stringResource(R.string.hub_mode_editing_exit_button_text), + ) + } } } } @@ -762,7 +718,7 @@ private fun PopupOnDismissCtaTile(onHidePopup: () -> Unit) { @Composable private fun RemoveButtonContent(spacerModifier: Modifier) { - Icon(Icons.Outlined.Delete, stringResource(R.string.button_to_remove_widget)) + Icon(Icons.Default.Close, stringResource(R.string.button_to_remove_widget)) Spacer(spacerModifier) Text( text = stringResource(R.string.button_to_remove_widget), @@ -804,6 +760,8 @@ private fun CommunalContent( is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(modifier) is CommunalContentModel.WidgetContent.DisabledWidget -> DisabledWidgetPlaceholder(model, viewModel, modifier) + is CommunalContentModel.WidgetContent.PendingWidget -> + PendingWidgetPlaceholder(model, modifier) is CommunalContentModel.CtaTileInViewMode -> CtaTileInViewModeContent(viewModel, modifier) is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier) is CommunalContentModel.Tutorial -> TutorialContent(modifier) @@ -929,36 +887,36 @@ private fun WidgetContent( Modifier.semantics { contentDescription = accessibilityLabel onClick(label = clickActionLabel, action = null) - val deleteAction = - CustomAccessibilityAction(removeWidgetActionLabel) { - contentListState.onRemove(index) + val deleteAction = + CustomAccessibilityAction(removeWidgetActionLabel) { + contentListState.onRemove(index) + contentListState.onSaveList() + true + } + val selectWidgetAction = + CustomAccessibilityAction(clickActionLabel) { + val currentWidgetKey = + index?.let { + keyAtIndexIfEditable(contentListState.list, index) + } + viewModel.setSelectedKey(currentWidgetKey) + true + } + + val actions = mutableListOf(deleteAction, selectWidgetAction) + + if (selectedIndex != null && selectedIndex != index) { + actions.add( + CustomAccessibilityAction(placeWidgetActionLabel) { + contentListState.onMove(selectedIndex!!, index) contentListState.onSaveList() + viewModel.setSelectedKey(null) true } - val selectWidgetAction = - CustomAccessibilityAction(clickActionLabel) { - val currentWidgetKey = - index?.let { - keyAtIndexIfEditable(contentListState.list, index) - } - viewModel.setSelectedKey(currentWidgetKey) - true - } - - val actions = mutableListOf(deleteAction, selectWidgetAction) - - if (selectedIndex != null && selectedIndex != index) { - actions.add( - CustomAccessibilityAction(placeWidgetActionLabel) { - contentListState.onMove(selectedIndex!!, index) - contentListState.onSaveList() - viewModel.setSelectedKey(null) - true - } - ) - } + ) + } - customActions = actions + customActions = actions } } ) { @@ -1074,13 +1032,43 @@ fun DisabledWidgetPlaceholder( Image( painter = rememberDrawablePainter(icon.loadDrawable(context)), contentDescription = stringResource(R.string.icon_description_for_disabled_widget), - modifier = Modifier.size(48.dp), + modifier = Modifier.size(Dimensions.IconSize), colorFilter = ColorFilter.colorMatrix(Colors.DisabledColorFilter), ) } } @Composable +fun PendingWidgetPlaceholder( + model: CommunalContentModel.WidgetContent.PendingWidget, + modifier: Modifier = Modifier, +) { + val context = LocalContext.current + val icon: Icon = + if (model.icon != null) { + Icon.createWithBitmap(model.icon) + } else { + Icon.createWithResource(context, android.R.drawable.sym_def_app_icon) + } + + Column( + modifier = + modifier.background( + MaterialTheme.colorScheme.surfaceVariant, + RoundedCornerShape(dimensionResource(system_app_widget_background_radius)) + ), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Image( + painter = rememberDrawablePainter(icon.loadDrawable(context)), + contentDescription = stringResource(R.string.icon_description_for_pending_widget), + modifier = Modifier.size(Dimensions.IconSize), + ) + } +} + +@Composable private fun SmartspaceContent( model: CommunalContentModel.Smartspace, modifier: Modifier = Modifier, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt index c109e517a581..abbf0ea892ec 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt @@ -58,15 +58,11 @@ fun Modifier.burnInAware( .collectAsState(initial = BurnInScaleViewModel()) return this.graphicsLayer { - val scale = - when { - scaleViewModel.scaleClockOnly && isClock -> scaleViewModel.scale - else -> 1f - } - this.translationX = if (isClock) 0F else translationX this.translationY = translationY this.alpha = alpha + + val scale = if (scaleViewModel.scaleClockOnly) scaleViewModel.scale else 1f this.scaleX = scale this.scaleY = scale } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt index 9f02201f1d81..4129c25901e5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt @@ -22,6 +22,7 @@ import android.view.View import android.view.WindowManager import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.layout import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.Constraints @@ -68,7 +69,7 @@ constructor( private val notificationPanelView: NotificationPanelView, ) { @Composable - fun SceneScope.LockIcon(modifier: Modifier = Modifier) { + fun SceneScope.LockIcon(overrideColor: Color? = null, modifier: Modifier = Modifier) { if (!KeyguardBottomAreaRefactor.isEnabled && !DeviceEntryUdfpsRefactor.isEnabled) { return } @@ -93,6 +94,7 @@ constructor( deviceEntryBackgroundViewModel.get(), falsingManager.get(), vibratorHelper.get(), + overrideColor, ) } } else { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt index 556bbbe9f997..c37d626ca8c5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/MediaCarouselSection.kt @@ -18,9 +18,11 @@ package com.android.systemui.keyguard.ui.composable.section import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import com.android.compose.animation.scene.SceneScope -import com.android.systemui.keyguard.ui.viewmodel.MediaCarouselViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardMediaViewModel import com.android.systemui.media.controls.ui.composable.MediaCarousel import com.android.systemui.media.controls.ui.controller.MediaCarouselController import com.android.systemui.media.controls.ui.view.MediaHost @@ -33,20 +35,15 @@ class MediaCarouselSection constructor( private val mediaCarouselController: MediaCarouselController, @param:Named(MediaModule.KEYGUARD) private val mediaHost: MediaHost, - private val mediaCarouselViewModel: MediaCarouselViewModel, + private val keyguardMediaViewModel: KeyguardMediaViewModel, ) { - private fun isVisible(): Boolean { - if (mediaCarouselController.mediaFrame == null) { - return false - } - return mediaCarouselViewModel.isMediaVisible - } - @Composable fun SceneScope.KeyguardMediaCarousel() { + val isMediaVisible by keyguardMediaViewModel.isMediaVisible.collectAsState() + MediaCarousel( - isVisible = ::isVisible, + isVisible = isMediaVisible, mediaHost = mediaHost, modifier = Modifier.fillMaxWidth(), carouselController = mediaCarouselController, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt index 241c171f7862..581f3a5cacff 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/media/controls/ui/composable/MediaCarousel.kt @@ -42,12 +42,12 @@ private object MediaCarousel { @Composable fun SceneScope.MediaCarousel( - isVisible: () -> Boolean, + isVisible: Boolean, mediaHost: MediaHost, modifier: Modifier = Modifier, carouselController: MediaCarouselController, ) { - if (!isVisible()) { + if (!isVisible) { return } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackContentHeight.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackContentHeight.kt new file mode 100644 index 000000000000..9f829cccfb32 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackContentHeight.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.notifications.ui.composable + +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.node.LayoutModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.invalidateMeasurement +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.IntOffset +import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView + +/** + * Modify element, which updates the height to be the same as the Notification stack height returned + * by the legacy Notification stack scroll view in [NotificationScrollView.intrinsicStackHeight]. + * + * @param view Notification stack scroll view + * @param padding extra padding in pixels to be added to the received content height. + */ +fun Modifier.notificationStackHeight(view: NotificationScrollView, padding: Int = 0) = + this then StackLayoutElement(view, padding) + +private data class StackLayoutElement( + val view: NotificationScrollView, + val padding: Int, +) : ModifierNodeElement<StackLayoutNode>() { + + override fun create(): StackLayoutNode = StackLayoutNode(view, padding) + + override fun update(node: StackLayoutNode) { + check(view == node.view) { "Trying to reuse the node with a new View." } + if (node.padding != padding) { + node.padding = padding + node.invalidateMeasureIfAttached() + } + } +} + +private class StackLayoutNode(val view: NotificationScrollView, var padding: Int) : + LayoutModifierNode, Modifier.Node() { + + private val stackHeightChangedListener = Runnable { invalidateMeasureIfAttached() } + + override fun onAttach() { + super.onAttach() + view.addStackHeightChangedListener(stackHeightChangedListener) + } + + override fun onDetach() { + super.onDetach() + view.removeStackHeightChangedListener(stackHeightChangedListener) + } + + override fun MeasureScope.measure( + measurable: Measurable, + constraints: Constraints + ): MeasureResult { + val contentHeight = padding + view.intrinsicStackHeight + val placeable = + measurable.measure( + constraints.copy(minHeight = contentHeight, maxHeight = contentHeight) + ) + + return layout(placeable.width, placeable.height) { placeable.place(IntOffset.Zero) } + } + + override fun toString(): String { + return "StackLayoutNode(view=$view padding:$padding)" + } + + fun invalidateMeasureIfAttached() { + if (isAttached) { + this.invalidateMeasurement() + } + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index 3ce0feb0af56..01d62a36fc62 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -40,6 +40,8 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment @@ -66,15 +68,18 @@ import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.NestedScrollBehavior import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.height +import com.android.compose.modifiers.thenIf import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius import com.android.systemui.res.R import com.android.systemui.scene.session.ui.composable.SaveableSession import com.android.systemui.scene.session.ui.composable.rememberSession import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.shade.ui.composable.ShadeHeader import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding +import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_CORNER_RADIUS import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel @@ -158,9 +163,11 @@ fun SceneScope.ConstrainedNotificationStack( @Composable fun SceneScope.NotificationScrollingStack( shadeSession: SaveableSession, + stackScrollView: NotificationScrollView, viewModel: NotificationsPlaceholderViewModel, maxScrimTop: () -> Float, shouldPunchHoleBehindScrim: Boolean, + shadeMode: ShadeMode, modifier: Modifier = Modifier, ) { val coroutineScope = rememberCoroutineScope() @@ -179,7 +186,12 @@ fun SceneScope.NotificationScrollingStack( with(density) { WindowInsets.systemBars.asPaddingValues().calculateBottomPadding().toPx() } val screenHeight = LocalRawScreenHeight.current - val stackHeight = viewModel.stackHeight.collectAsState() + /** + * The height in px of the contents of notification stack. Depending on the number of + * notifications, this can exceed the space available on screen to show notifications, at which + * point the notification stack should become scrollable. + */ + val stackHeight = remember { mutableIntStateOf(0) } val scrimRounding = viewModel.shadeScrimRounding.collectAsState(ShadeScrimRounding()) @@ -212,7 +224,7 @@ fun SceneScope.NotificationScrollingStack( // if contentHeight drops below minimum visible scrim height while scrim is // expanded, reset scrim offset. LaunchedEffect(stackHeight, scrimOffset) { - snapshotFlow { stackHeight.value < minVisibleScrimHeight() && scrimOffset.value < 0f } + snapshotFlow { stackHeight.intValue < minVisibleScrimHeight() && scrimOffset.value < 0f } .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.snapTo(0f) } } @@ -233,6 +245,27 @@ fun SceneScope.NotificationScrollingStack( } } + val scrimNestedScrollConnection = + shadeSession.rememberSession( + scrimOffset, + maxScrimTop, + minScrimTop, + isCurrentGestureOverscroll, + ) { + NotificationScrimNestedScrollConnection( + scrimOffset = { scrimOffset.value }, + snapScrimOffset = { value -> coroutineScope.launch { scrimOffset.snapTo(value) } }, + animateScrimOffset = { value -> + coroutineScope.launch { scrimOffset.animateTo(value) } + }, + minScrimOffset = minScrimOffset, + maxScrimOffset = 0f, + contentHeight = { stackHeight.intValue.toFloat() }, + minVisibleScrimHeight = minVisibleScrimHeight, + isCurrentGestureOverscroll = { isCurrentGestureOverscroll.value }, + ) + } + Box( modifier = modifier @@ -307,34 +340,16 @@ fun SceneScope.NotificationScrollingStack( topBehavior = NestedScrollBehavior.EdgeWithPreview, isExternalOverscrollGesture = { isCurrentGestureOverscroll.value } ) - .nestedScroll( - shadeSession.rememberSession( - scrimOffset, - maxScrimTop, - minScrimTop, - isCurrentGestureOverscroll, - ) { - NotificationScrimNestedScrollConnection( - scrimOffset = { scrimOffset.value }, - snapScrimOffset = { value -> - coroutineScope.launch { scrimOffset.snapTo(value) } - }, - animateScrimOffset = { value -> - coroutineScope.launch { scrimOffset.animateTo(value) } - }, - minScrimOffset = minScrimOffset, - maxScrimOffset = 0f, - contentHeight = { stackHeight.value }, - minVisibleScrimHeight = minVisibleScrimHeight, - isCurrentGestureOverscroll = { - isCurrentGestureOverscroll.value - }, - ) - } - ) + .thenIf(shadeMode == ShadeMode.Single) { + Modifier.nestedScroll(scrimNestedScrollConnection) + } .verticalScroll(scrollState) .fillMaxWidth() - .height { (stackHeight.value + navBarHeight).roundToInt() }, + .notificationStackHeight( + view = stackScrollView, + padding = navBarHeight.toInt() + ) + .onSizeChanged { size -> stackHeight.intValue = size.height }, ) } HeadsUpNotificationSpace(viewModel = viewModel) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt index 5f84dd47a240..2f241cec37ee 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt @@ -181,6 +181,7 @@ fun FooterActions( val horizontalPadding = dimensionResource(R.dimen.qs_content_horizontal_padding) Row( modifier + .sysuiResTag("qs_footer_actions") .fillMaxWidth() .graphicsLayer { this.alpha = alpha } .then(backgroundModifier) 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 ec9136d50e4c..6ae0efa46d19 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 @@ -36,6 +36,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.displayCutoutPadding import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -68,6 +69,8 @@ import com.android.compose.animation.scene.animateSceneFloatAsState import com.android.compose.modifiers.thenIf import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.battery.BatteryMeterViewController +import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation +import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dagger.SysUISingleton @@ -83,14 +86,17 @@ import com.android.systemui.res.R import com.android.systemui.scene.session.ui.composable.SaveableSession import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.ComposableScene +import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.shade.ui.composable.CollapsedShadeHeader import com.android.systemui.shade.ui.composable.ExpandedShadeHeader import com.android.systemui.shade.ui.composable.Shade import com.android.systemui.shade.ui.composable.ShadeHeader +import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.phone.ui.StatusBarIconController import com.android.systemui.statusbar.phone.ui.TintedIconManager +import dagger.Lazy import javax.inject.Inject import javax.inject.Named import kotlin.math.roundToInt @@ -105,6 +111,7 @@ class QuickSettingsScene constructor( @Application private val applicationScope: CoroutineScope, private val shadeSession: SaveableSession, + private val notificationStackScrollView: Lazy<NotificationScrollView>, private val viewModel: QuickSettingsSceneViewModel, private val notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, private val tintedIconManagerFactory: TintedIconManager.Factory, @@ -127,6 +134,7 @@ constructor( modifier: Modifier, ) { QuickSettingsScene( + notificationStackScrollView = notificationStackScrollView.get(), viewModel = viewModel, notificationsPlaceholderViewModel = notificationsPlaceholderViewModel, createTintedIconManager = tintedIconManagerFactory::create, @@ -142,6 +150,7 @@ constructor( @Composable private fun SceneScope.QuickSettingsScene( + notificationStackScrollView: NotificationScrollView, viewModel: QuickSettingsSceneViewModel, notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, @@ -152,6 +161,8 @@ private fun SceneScope.QuickSettingsScene( modifier: Modifier = Modifier, shadeSession: SaveableSession, ) { + val cutoutLocation = LocalDisplayCutout.current.location + val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState() val contentAlpha by animateFloatAsState( @@ -183,6 +194,9 @@ private fun SceneScope.QuickSettingsScene( // scene (and not the one under it) during a scene transition. Modifier.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen) } + .thenIf(cutoutLocation != CutoutLocation.CENTER) { + Modifier.displayCutoutPadding() + }, ) { val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState() val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsState() @@ -280,7 +294,8 @@ private fun SceneScope.QuickSettingsScene( } Column( - modifier = shadeHeaderAndQuickSettingsModifier, + modifier = + shadeHeaderAndQuickSettingsModifier.sysuiResTag("expanded_qs_scroll_view"), ) { when (LocalWindowSizeClass.current.widthSizeClass) { WindowWidthSizeClass.Compact -> @@ -320,7 +335,6 @@ private fun SceneScope.QuickSettingsScene( createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, statusBarIconController = statusBarIconController, - modifier = Modifier.padding(horizontal = 16.dp), ) } Spacer(modifier = Modifier.height(16.dp)) @@ -329,11 +343,13 @@ private fun SceneScope.QuickSettingsScene( viewModel.qsSceneAdapter, { viewModel.qsSceneAdapter.qsHeight }, isSplitShade = false, - modifier = Modifier.sysuiResTag("expanded_qs_scroll_view") + modifier = Modifier.sysuiResTag("quick_settings_panel") ) + val isMediaVisible by viewModel.isMediaVisible.collectAsState() + MediaCarousel( - isVisible = viewModel::isMediaVisible, + isVisible = isMediaVisible, mediaHost = mediaHost, modifier = Modifier.fillMaxWidth(), carouselController = mediaCarouselController, @@ -350,10 +366,12 @@ private fun SceneScope.QuickSettingsScene( ) } NotificationScrollingStack( + stackScrollView = notificationStackScrollView, viewModel = notificationsPlaceholderViewModel, shadeSession = shadeSession, maxScrimTop = { screenHeight }, shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim, + shadeMode = ShadeMode.Single, modifier = Modifier.fillMaxWidth().offset { IntOffset(x = 0, y = screenHeight.roundToInt()) }, ) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt index 7eaebc21355d..cbaa89438f2e 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt @@ -6,14 +6,17 @@ import com.android.systemui.bouncer.ui.composable.Bouncer import com.android.systemui.notifications.ui.composable.Notifications import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse +import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade import com.android.systemui.scene.ui.composable.transitions.bouncerToGoneTransition import com.android.systemui.scene.ui.composable.transitions.goneToQuickSettingsTransition import com.android.systemui.scene.ui.composable.transitions.goneToShadeTransition +import com.android.systemui.scene.ui.composable.transitions.goneToSplitShadeTransition import com.android.systemui.scene.ui.composable.transitions.lockscreenToBouncerTransition import com.android.systemui.scene.ui.composable.transitions.lockscreenToCommunalTransition import com.android.systemui.scene.ui.composable.transitions.lockscreenToGoneTransition import com.android.systemui.scene.ui.composable.transitions.lockscreenToQuickSettingsTransition import com.android.systemui.scene.ui.composable.transitions.lockscreenToShadeTransition +import com.android.systemui.scene.ui.composable.transitions.lockscreenToSplitShadeTransition import com.android.systemui.scene.ui.composable.transitions.shadeToQuickSettingsTransition import com.android.systemui.shade.ui.composable.Shade @@ -38,6 +41,13 @@ val SceneContainerTransitions = transitions { from( Scenes.Gone, to = Scenes.Shade, + key = ToSplitShade, + ) { + goneToSplitShadeTransition() + } + from( + Scenes.Gone, + to = Scenes.Shade, key = SlightlyFasterShadeCollapse, ) { goneToShadeTransition(durationScale = 0.9) @@ -49,6 +59,13 @@ val SceneContainerTransitions = transitions { from( Scenes.Lockscreen, to = Scenes.Shade, + key = ToSplitShade, + ) { + lockscreenToSplitShadeTransition() + } + from( + Scenes.Lockscreen, + to = Scenes.Shade, key = SlightlyFasterShadeCollapse, ) { lockscreenToShadeTransition(durationScale = 0.9) @@ -68,5 +85,9 @@ val SceneContainerTransitions = transitions { Notifications.Elements.NotificationScrim, y = { Shade.Dimensions.ScrimOverscrollLimit } ) + translate( + Shade.Elements.SplitShadeStartColumn, + y = { Shade.Dimensions.ScrimOverscrollLimit } + ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt new file mode 100644 index 000000000000..f14ff76df5db --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToSplitShadeTransition.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.scene.ui.composable.transitions + +import com.android.compose.animation.scene.TransitionBuilder + +fun TransitionBuilder.goneToSplitShadeTransition( + durationScale: Double = 1.0, +) { + toSplitShadeTransition(durationScale) +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt new file mode 100644 index 000000000000..70c343ceacda --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToSplitShadeTransition.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.scene.ui.composable.transitions + +import com.android.compose.animation.scene.TransitionBuilder + +fun TransitionBuilder.lockscreenToSplitShadeTransition( + durationScale: Double = 1.0, +) { + toSplitShadeTransition(durationScale = durationScale) +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToSplitShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToSplitShadeTransition.kt new file mode 100644 index 000000000000..a8315c05c7d1 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToSplitShadeTransition.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.scene.ui.composable.transitions + +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.ui.unit.IntSize +import com.android.compose.animation.scene.TransitionBuilder +import com.android.compose.animation.scene.UserActionDistance +import com.android.compose.animation.scene.UserActionDistanceScope +import com.android.systemui.notifications.ui.composable.Notifications +import com.android.systemui.qs.ui.composable.QuickSettings +import com.android.systemui.shade.ui.composable.Shade +import com.android.systemui.shade.ui.composable.ShadeHeader +import kotlin.time.Duration.Companion.milliseconds + +fun TransitionBuilder.toSplitShadeTransition( + durationScale: Double = 1.0, +) { + spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt()) + swipeSpec = + spring( + stiffness = Spring.StiffnessMediumLow, + visibilityThreshold = Shade.Dimensions.ScrimVisibilityThreshold, + ) + distance = + object : UserActionDistance { + override fun UserActionDistanceScope.absoluteDistance( + fromSceneSize: IntSize, + orientation: Orientation, + ): Float { + return fromSceneSize.height.toFloat() * 2 / 3f + } + } + + fractionRange(end = .33f) { fade(Shade.Elements.BackgroundScrim) } + + fractionRange(start = .33f) { + fade(ShadeHeader.Elements.Clock) + fade(ShadeHeader.Elements.CollapsedContentStart) + fade(ShadeHeader.Elements.CollapsedContentEnd) + fade(ShadeHeader.Elements.PrivacyChip) + fade(QuickSettings.Elements.SplitShadeQuickSettings) + fade(QuickSettings.Elements.FooterActions) + fade(Notifications.Elements.NotificationScrim) + } +} + +private val DefaultDuration = 500.milliseconds 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 0bd38a1daf43..709a416c0366 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 @@ -50,6 +50,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.max import androidx.compose.ui.viewinterop.AndroidView import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexScenePicker @@ -63,6 +64,7 @@ import com.android.systemui.battery.BatteryMeterView import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout +import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.privacy.OngoingPrivacyChip import com.android.systemui.res.R @@ -77,6 +79,7 @@ import com.android.systemui.statusbar.phone.ui.TintedIconManager import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernShadeCarrierGroupMobileView import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.ShadeCarrierGroupMobileIconViewModel import com.android.systemui.statusbar.policy.Clock +import kotlin.math.max object ShadeHeader { object Elements { @@ -121,7 +124,11 @@ fun SceneScope.CollapsedShadeHeader( } val cutoutWidth = LocalDisplayCutout.current.width() + val cutoutHeight = LocalDisplayCutout.current.height() + val cutoutTop = LocalDisplayCutout.current.top val cutoutLocation = LocalDisplayCutout.current.location + val horizontalPadding = + max(LocalScreenCornerRadius.current / 2f, Shade.Dimensions.HorizontalPadding) val useExpandedFormat by remember(cutoutLocation) { @@ -140,7 +147,7 @@ fun SceneScope.CollapsedShadeHeader( contents = listOf( { - Row { + Row(modifier = Modifier.padding(horizontal = horizontalPadding)) { Clock( scale = 1f, viewModel = viewModel, @@ -157,7 +164,12 @@ fun SceneScope.CollapsedShadeHeader( }, { if (isPrivacyChipVisible) { - Box(modifier = Modifier.height(CollapsedHeight).fillMaxWidth()) { + Box( + modifier = + Modifier.height(CollapsedHeight) + .fillMaxWidth() + .padding(horizontal = horizontalPadding) + ) { PrivacyChip( viewModel = viewModel, modifier = Modifier.align(Alignment.CenterEnd), @@ -166,9 +178,13 @@ fun SceneScope.CollapsedShadeHeader( } else { Row( horizontalArrangement = Arrangement.End, - modifier = Modifier.element(ShadeHeader.Elements.CollapsedContentEnd) + modifier = + Modifier.element(ShadeHeader.Elements.CollapsedContentEnd) + .padding(horizontal = horizontalPadding) ) { - SystemIconContainer { + SystemIconContainer( + modifier = Modifier.align(Alignment.CenterVertically) + ) { when (LocalWindowSizeClass.current.widthSizeClass) { WindowWidthSizeClass.Medium, WindowWidthSizeClass.Expanded -> @@ -206,7 +222,7 @@ fun SceneScope.CollapsedShadeHeader( val screenWidth = constraints.maxWidth val cutoutWidthPx = cutoutWidth.roundToPx() - val height = CollapsedHeight.roundToPx() + val height = max(cutoutHeight + (cutoutTop * 2), CollapsedHeight).roundToPx() val childConstraints = Constraints.fixed((screenWidth - cutoutWidthPx) / 2, height) val startMeasurable = measurables[0][0] 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 4ad8b9f47f70..42e6fccab0ae 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 @@ -31,6 +31,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.asPaddingValues +import androidx.compose.foundation.layout.displayCutoutPadding import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -67,6 +68,9 @@ import com.android.compose.animation.scene.animateSceneFloatAsState import com.android.compose.modifiers.padding import com.android.compose.modifiers.thenIf import com.android.systemui.battery.BatteryMeterViewController +import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation +import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout +import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius import com.android.systemui.dagger.SysUISingleton import com.android.systemui.media.controls.ui.composable.MediaCarousel import com.android.systemui.media.controls.ui.controller.MediaCarouselController @@ -84,9 +88,11 @@ import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.ComposableScene import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel +import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.phone.ui.StatusBarIconController import com.android.systemui.statusbar.phone.ui.TintedIconManager +import dagger.Lazy import javax.inject.Inject import javax.inject.Named import kotlin.math.roundToInt @@ -97,6 +103,7 @@ object Shade { val MediaCarousel = ElementKey("ShadeMediaCarousel") val BackgroundScrim = ElementKey("ShadeBackgroundScrim", scenePicker = LowestZIndexScenePicker) + val SplitShadeStartColumn = ElementKey("SplitShadeStartColumn") } object Dimensions { @@ -121,6 +128,7 @@ class ShadeScene @Inject constructor( private val shadeSession: SaveableSession, + private val notificationStackScrollView: Lazy<NotificationScrollView>, private val viewModel: ShadeSceneViewModel, private val tintedIconManagerFactory: TintedIconManager.Factory, private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory, @@ -139,6 +147,7 @@ constructor( modifier: Modifier, ) = ShadeScene( + notificationStackScrollView.get(), viewModel = viewModel, createTintedIconManager = tintedIconManagerFactory::create, createBatteryMeterViewController = batteryMeterViewControllerFactory::create, @@ -158,6 +167,7 @@ constructor( @Composable private fun SceneScope.ShadeScene( + notificationStackScrollView: NotificationScrollView, viewModel: ShadeSceneViewModel, createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController, @@ -171,6 +181,7 @@ private fun SceneScope.ShadeScene( when (shadeMode) { is ShadeMode.Single -> SingleShade( + notificationStackScrollView = notificationStackScrollView, viewModel = viewModel, createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, @@ -182,6 +193,7 @@ private fun SceneScope.ShadeScene( ) is ShadeMode.Split -> SplitShade( + notificationStackScrollView = notificationStackScrollView, viewModel = viewModel, createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, @@ -197,6 +209,7 @@ private fun SceneScope.ShadeScene( @Composable private fun SceneScope.SingleShade( + notificationStackScrollView: NotificationScrollView, viewModel: ShadeSceneViewModel, createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController, @@ -206,6 +219,8 @@ private fun SceneScope.SingleShade( modifier: Modifier = Modifier, shadeSession: SaveableSession, ) { + val cutoutLocation = LocalDisplayCutout.current.location + val maxNotifScrimTop = remember { mutableStateOf(0f) } val tileSquishiness by animateSceneFloatAsState( @@ -214,6 +229,7 @@ private fun SceneScope.SingleShade( canOverflow = false ) val isClickable by viewModel.isClickable.collectAsState() + val isMediaVisible by viewModel.isMediaVisible.collectAsState() val shouldPunchHoleBehindScrim = layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade) || @@ -241,19 +257,21 @@ private fun SceneScope.SingleShade( Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = - Modifier.fillMaxWidth().thenIf(isClickable) { - Modifier.clickable(onClick = { viewModel.onContentClicked() }) - } + Modifier.fillMaxWidth() + .thenIf(isClickable) { + Modifier.clickable( + onClick = { viewModel.onContentClicked() } + ) + } + .thenIf(cutoutLocation != CutoutLocation.CENTER) { + Modifier.displayCutoutPadding() + }, ) { CollapsedShadeHeader( viewModel = viewModel.shadeHeaderViewModel, createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, statusBarIconController = statusBarIconController, - modifier = - Modifier.padding( - horizontal = Shade.Dimensions.HorizontalPadding - ) ) Box(Modifier.element(QuickSettings.Elements.QuickQuickSettings)) { QuickSettings( @@ -265,7 +283,7 @@ private fun SceneScope.SingleShade( } MediaCarousel( - isVisible = viewModel::isMediaVisible, + isVisible = isMediaVisible, mediaHost = mediaHost, modifier = Modifier.fillMaxWidth(), carouselController = mediaCarouselController, @@ -277,8 +295,10 @@ private fun SceneScope.SingleShade( { NotificationScrollingStack( shadeSession = shadeSession, + stackScrollView = notificationStackScrollView, viewModel = viewModel.notifications, maxScrimTop = { maxNotifScrimTop.value }, + shadeMode = ShadeMode.Single, shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim, ) }, @@ -303,6 +323,7 @@ private fun SceneScope.SingleShade( @Composable private fun SceneScope.SplitShade( + notificationStackScrollView: NotificationScrollView, viewModel: ShadeSceneViewModel, createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController, @@ -312,6 +333,8 @@ private fun SceneScope.SplitShade( modifier: Modifier = Modifier, shadeSession: SaveableSession, ) { + val screenCornerRadius = LocalScreenCornerRadius.current + val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState() val isCustomizerShowing by viewModel.qsSceneAdapter.isCustomizerShowing.collectAsState() val customizingAnimationDuration by @@ -320,7 +343,11 @@ private fun SceneScope.SplitShade( val footerActionsViewModel = remember(lifecycleOwner, viewModel) { viewModel.getFooterActionsViewModel(lifecycleOwner) } val tileSquishiness by - animateSceneFloatAsState(value = 1f, key = QuickSettings.SharedValues.TilesSquishiness) + animateSceneFloatAsState( + value = 1f, + key = QuickSettings.SharedValues.TilesSquishiness, + canOverflow = false, + ) val unfoldTranslationXForStartSide by viewModel .unfoldTranslationX( @@ -366,6 +393,8 @@ private fun SceneScope.SplitShade( viewModel.notifications.setAlphaForBrightnessMirror(contentAlpha) DisposableEffect(Unit) { onDispose { viewModel.notifications.setAlphaForBrightnessMirror(1f) } } + val isMediaVisible by viewModel.isMediaVisible.collectAsState() + val brightnessMirrorShowingModifier = Modifier.graphicsLayer { alpha = contentAlpha } Box( @@ -388,8 +417,7 @@ private fun SceneScope.SplitShade( createBatteryMeterViewController = createBatteryMeterViewController, statusBarIconController = statusBarIconController, modifier = - Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding) - .then(brightnessMirrorShowingModifier) + Modifier.then(brightnessMirrorShowingModifier) .padding( horizontal = { unfoldTranslationXForStartSide.roundToInt() }, ) @@ -398,9 +426,9 @@ private fun SceneScope.SplitShade( Row(modifier = Modifier.fillMaxWidth().weight(1f)) { Box( modifier = - Modifier.weight(1f).graphicsLayer { - translationX = unfoldTranslationXForStartSide - }, + Modifier.element(Shade.Elements.SplitShadeStartColumn) + .weight(1f) + .graphicsLayer { translationX = unfoldTranslationXForStartSide }, ) { BrightnessMirror( viewModel = viewModel.brightnessMirrorViewModel, @@ -441,7 +469,7 @@ private fun SceneScope.SplitShade( } MediaCarousel( - isVisible = viewModel::isMediaVisible, + isVisible = isMediaVisible, mediaHost = mediaHost, modifier = Modifier.fillMaxWidth(), carouselController = mediaCarouselController, @@ -461,13 +489,15 @@ private fun SceneScope.SplitShade( NotificationScrollingStack( shadeSession = shadeSession, + stackScrollView = notificationStackScrollView, viewModel = viewModel.notifications, maxScrimTop = { 0f }, shouldPunchHoleBehindScrim = false, + shadeMode = ShadeMode.Split, modifier = Modifier.weight(1f) .fillMaxHeight() - .padding(bottom = navBarBottomHeight) + .padding(end = screenCornerRadius / 2f, bottom = navBarBottomHeight) .then(brightnessMirrorShowingModifier) ) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt index 79d17efcacc1..44b221c1f5e1 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncButtonComponent.kt @@ -16,6 +16,7 @@ package com.android.systemui.volume.panel.component.anc.ui.composable +import android.view.Gravity import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -27,9 +28,15 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.clearAndSetSemantics @@ -39,6 +46,7 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import com.android.systemui.res.R import com.android.systemui.volume.panel.component.anc.ui.viewmodel.AncViewModel +import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope import javax.inject.Inject @@ -54,15 +62,22 @@ constructor( override fun VolumePanelComposeScope.Content(modifier: Modifier) { val slice by viewModel.buttonSlice.collectAsState() val label = stringResource(R.string.volume_panel_noise_control_title) + val screenWidth: Float = + with(LocalDensity.current) { LocalConfiguration.current.screenWidthDp.dp.toPx() } + var gravity by remember { mutableIntStateOf(Gravity.CENTER_HORIZONTAL) } val isClickable = viewModel.isClickable(slice) val onClick = if (isClickable) { - { ancPopup.show(null) } + { with(ancPopup) { show(null, gravity) } } } else { null } + Column( - modifier = modifier, + modifier = + modifier.onGloballyPositioned { + gravity = VolumePanelPopup.calculateGravity(it, screenWidth) + }, verticalArrangement = Arrangement.spacedBy(12.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt index e1ee01e78566..d53dbf9ddd48 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/AncPopup.kt @@ -16,6 +16,7 @@ package com.android.systemui.volume.panel.component.anc.ui.composable +import android.view.Gravity import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.MaterialTheme @@ -47,9 +48,10 @@ constructor( ) { /** Shows a popup with the [expandable] animation. */ - fun show(expandable: Expandable?) { + fun show(expandable: Expandable?, horizontalGravity: Int) { uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_ANC_POPUP_SHOWN) - volumePanelPopup.show(expandable, { Title() }, { Content(it) }) + val gravity = horizontalGravity or Gravity.BOTTOM + volumePanelPopup.show(expandable, gravity, { Title() }, { Content(it) }) } @Composable diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt index 0893b9d4c580..f11c3a5852d8 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ButtonComponent.kt @@ -16,6 +16,7 @@ package com.android.systemui.volume.panel.component.button.ui.composable +import android.view.Gravity import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -29,8 +30,14 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription @@ -41,6 +48,7 @@ import com.android.compose.animation.Expandable import com.android.systemui.animation.Expandable import com.android.systemui.common.ui.compose.Icon import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel +import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup.Companion.calculateGravity import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope import kotlinx.coroutines.flow.StateFlow @@ -48,7 +56,7 @@ import kotlinx.coroutines.flow.StateFlow /** [ComposeVolumePanelUiComponent] implementing a clickable button from a bottom row. */ class ButtonComponent( private val viewModelFlow: StateFlow<ButtonViewModel?>, - private val onClick: (Expandable) -> Unit + private val onClick: (expandable: Expandable, horizontalGravity: Int) -> Unit ) : ComposeVolumePanelUiComponent { @Composable @@ -57,8 +65,13 @@ class ButtonComponent( val viewModel = viewModelByState ?: return val label = viewModel.label.toString() + val screenWidth: Float = + with(LocalDensity.current) { LocalConfiguration.current.screenWidthDp.dp.toPx() } + var gravity by remember { mutableIntStateOf(Gravity.CENTER_HORIZONTAL) } + Column( - modifier = modifier, + modifier = + modifier.onGloballyPositioned { gravity = calculateGravity(it, screenWidth) }, verticalArrangement = Arrangement.spacedBy(12.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { @@ -82,7 +95,7 @@ class ButtonComponent( } else { MaterialTheme.colorScheme.onSurface }, - onClick = onClick, + onClick = { onClick(it, gravity) }, ) { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt index bb4e9574c602..3b1bf2ab9dcd 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/popup/ui/composable/VolumePanelPopup.kt @@ -17,6 +17,7 @@ package com.android.systemui.volume.panel.component.popup.ui.composable import android.view.Gravity +import androidx.annotation.GravityInt import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -31,6 +32,8 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.layout.boundsInRoot import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.paneTitle @@ -60,13 +63,14 @@ constructor( */ fun show( expandable: Expandable?, + @GravityInt gravity: Int, title: @Composable (SystemUIDialog) -> Unit, content: @Composable (SystemUIDialog) -> Unit, ) { val dialog = dialogFactory.create( theme = R.style.Theme_VolumePanel_Popup, - dialogGravity = Gravity.BOTTOM, + dialogGravity = gravity, ) { PopupComposable(it, title, content) } @@ -122,4 +126,23 @@ constructor( } } } + + companion object { + + /** + * Returns absolute ([Gravity.LEFT], [Gravity.RIGHT] or [Gravity.CENTER_HORIZONTAL]) + * [GravityInt] for the popup based on the [coordinates] global position relative to the + * [screenWidthPx]. + */ + @GravityInt + fun calculateGravity(coordinates: LayoutCoordinates, screenWidthPx: Float): Int { + val bottomCenter: Float = coordinates.boundsInRoot().bottomCenter.x + val rootBottomCenter: Float = screenWidthPx / 2 + return when { + bottomCenter < rootBottomCenter -> Gravity.LEFT + bottomCenter > rootBottomCenter -> Gravity.RIGHT + else -> Gravity.CENTER_HORIZONTAL + } + } + } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt index 12d2bc2e274b..d41acd9e852c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt @@ -16,6 +16,7 @@ package com.android.systemui.volume.panel.component.spatialaudio.ui.composable +import android.view.Gravity import androidx.compose.foundation.basicMarquee import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme @@ -47,14 +48,15 @@ constructor( ) { /** Shows a popup with the [expandable] animation. */ - fun show(expandable: Expandable) { + fun show(expandable: Expandable, horizontalGravity: Int) { uiEventLogger.logWithPosition( VolumePanelUiEvent.VOLUME_PANEL_SPATIAL_AUDIO_POP_UP_SHOWN, 0, null, viewModel.spatialAudioButtons.value.indexOfFirst { it.button.isActive } ) - volumePanelPopup.show(expandable, { Title() }, { Content(it) }) + val gravity = horizontalGravity or Gravity.BOTTOM + volumePanelPopup.show(expandable, gravity, { Title() }, { Content(it) }) } @Composable diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt index a3467f2ab78e..1def7fe95a4b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt @@ -20,6 +20,8 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.core.AnimationSpec +import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.tween import androidx.compose.animation.core.updateTransition import androidx.compose.animation.expandVertically @@ -28,10 +30,8 @@ import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.shrinkVertically -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -39,6 +39,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.IconButtonDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -49,15 +50,21 @@ import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.compose.PlatformSliderColors +import com.android.compose.modifiers.padding import com.android.systemui.res.R import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel private const val EXPAND_DURATION_MILLIS = 500 +private const val COLLAPSE_EXPAND_BUTTON_DELAY_MILLIS = 350 private const val COLLAPSE_DURATION_MILLIS = 300 +private const val EXPAND_BUTTON_ANIMATION_DURATION_MILLIS = 350 +private const val TOP_SLIDER_ANIMATION_DURATION_MILLIS = 400 private const val SHRINK_FRACTION = 0.55f private const val SCALE_FRACTION = 0.9f +private const val EXPAND_BUTTON_SCALE = 0.8f /** Volume sliders laid out in a collapsable column */ @OptIn(ExperimentalAnimationApi::class) @@ -73,14 +80,15 @@ fun ColumnVolumeSliders( require(viewModels.isNotEmpty()) val transition = updateTransition(isExpanded, label = "CollapsableSliders") Column(modifier = modifier) { - Row( + Box( modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp), ) { val sliderViewModel: SliderViewModel = viewModels.first() val sliderState by viewModels.first().slider.collectAsState() + val sliderPadding by topSliderPadding(isExpandable) + VolumeSlider( - modifier = Modifier.weight(1f), + modifier = Modifier.padding(end = { sliderPadding.roundToPx() }).fillMaxWidth(), state = sliderState, onValueChange = { newValue: Float -> sliderViewModel.onValueChanged(sliderState, newValue) @@ -90,21 +98,13 @@ fun ColumnVolumeSliders( sliderColors = sliderColors, ) - val expandButtonStateDescription = - if (isExpanded) stringResource(R.string.volume_panel_expanded_sliders) - else stringResource(R.string.volume_panel_collapsed_sliders) - if (isExpandable) { - ExpandButton( - modifier = - Modifier.semantics { - role = Role.Switch - stateDescription = expandButtonStateDescription - }, - isExpanded = isExpanded, - onExpandedChanged = onExpandedChanged, - sliderColors = sliderColors, - ) - } + ExpandButton( + modifier = Modifier.align(Alignment.CenterEnd), + isExpanded = isExpanded, + isExpandable = isExpandable, + onExpandedChanged = onExpandedChanged, + sliderColors = sliderColors, + ) } transition.AnimatedVisibility( visible = { it || !isExpandable }, @@ -147,30 +147,48 @@ fun ColumnVolumeSliders( @Composable private fun ExpandButton( isExpanded: Boolean, + isExpandable: Boolean, onExpandedChanged: (Boolean) -> Unit, sliderColors: PlatformSliderColors, modifier: Modifier = Modifier, ) { - IconButton( - modifier = modifier.size(64.dp), - onClick = { onExpandedChanged(!isExpanded) }, - colors = - IconButtonDefaults.filledIconButtonColors( - containerColor = sliderColors.indicatorColor, - contentColor = sliderColors.iconColor - ), + val expandButtonStateDescription = + if (isExpanded) { + stringResource(R.string.volume_panel_expanded_sliders) + } else { + stringResource(R.string.volume_panel_collapsed_sliders) + } + AnimatedVisibility( + modifier = modifier, + visible = isExpandable, + enter = expandButtonEnterTransition(), + exit = expandButtonExitTransition(), ) { - Icon( - painter = - painterResource( - if (isExpanded) { - R.drawable.ic_filled_arrow_down - } else { - R.drawable.ic_filled_arrow_up - } + IconButton( + modifier = + Modifier.size(64.dp).semantics { + role = Role.Switch + stateDescription = expandButtonStateDescription + }, + onClick = { onExpandedChanged(!isExpanded) }, + colors = + IconButtonDefaults.filledIconButtonColors( + containerColor = sliderColors.indicatorColor, + contentColor = sliderColors.iconColor ), - contentDescription = null, - ) + ) { + Icon( + painter = + painterResource( + if (isExpanded) { + R.drawable.ic_filled_arrow_down + } else { + R.drawable.ic_filled_arrow_up + } + ), + contentDescription = null, + ) + } } } @@ -204,3 +222,63 @@ private fun exitTransition(index: Int, totalCount: Int): ExitTransition { ) + fadeOut(animationSpec = tween(durationMillis = exitDuration)) } + +private fun expandButtonEnterTransition(): EnterTransition { + return fadeIn( + tween( + delayMillis = COLLAPSE_EXPAND_BUTTON_DELAY_MILLIS, + durationMillis = EXPAND_BUTTON_ANIMATION_DURATION_MILLIS, + ) + ) + + scaleIn( + animationSpec = + tween( + delayMillis = COLLAPSE_EXPAND_BUTTON_DELAY_MILLIS, + durationMillis = EXPAND_BUTTON_ANIMATION_DURATION_MILLIS, + ), + initialScale = EXPAND_BUTTON_SCALE, + ) +} + +private fun expandButtonExitTransition(): ExitTransition { + return fadeOut( + tween( + delayMillis = EXPAND_DURATION_MILLIS, + durationMillis = EXPAND_BUTTON_ANIMATION_DURATION_MILLIS, + ) + ) + + scaleOut( + animationSpec = + tween( + delayMillis = EXPAND_DURATION_MILLIS, + durationMillis = EXPAND_BUTTON_ANIMATION_DURATION_MILLIS, + ), + targetScale = EXPAND_BUTTON_SCALE, + ) +} + +@Composable +private fun topSliderPadding(isExpandable: Boolean): State<Dp> { + val animationSpec: AnimationSpec<Dp> = + if (isExpandable) { + tween( + delayMillis = COLLAPSE_DURATION_MILLIS, + durationMillis = TOP_SLIDER_ANIMATION_DURATION_MILLIS, + ) + } else { + tween( + delayMillis = EXPAND_DURATION_MILLIS, + durationMillis = TOP_SLIDER_ANIMATION_DURATION_MILLIS, + ) + } + return animateDpAsState( + targetValue = + if (isExpandable) { + 72.dp + } else { + 0.dp + }, + animationSpec = animationSpec, + label = "TopVolumeSliderPadding" + ) +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt index a46f4e5fef1a..cb3867f209e3 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt @@ -34,12 +34,15 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.CustomAccessibilityAction import androidx.compose.ui.semantics.ProgressBarRangeInfo import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.customActions import androidx.compose.ui.semantics.disabled import androidx.compose.ui.semantics.progressBarRangeInfo import androidx.compose.ui.semantics.setProgress +import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.unit.dp import com.android.compose.PlatformSlider import com.android.compose.PlatformSliderColors @@ -60,14 +63,31 @@ fun VolumeSlider( PlatformSlider( modifier = modifier.clearAndSetSemantics { - if (!state.isEnabled) disabled() - contentDescription = - state.disabledMessage?.let { "${state.label}, $it" } ?: state.label - - // provide a not animated value to the a11y because it fails to announce the - // settled value when it changes rapidly. if (state.isEnabled) { - progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange) + contentDescription = state.label + state.a11yClickDescription?.let { + customActions = + listOf( + CustomAccessibilityAction( + it, + ) { + onIconTapped() + true + } + ) + } + + state.a11yStateDescription?.let { stateDescription = it } + ?: run { + // provide a not animated value to the a11y because it fails to announce + // the settled value when it changes rapidly. + progressBarRangeInfo = + ProgressBarRangeInfo(state.value, state.valueRange) + } + } else { + disabled() + contentDescription = + state.disabledMessage?.let { "${state.label}, $it" } ?: state.label } setProgress { targetValue -> val targetDirection = diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt index fdf8ee872019..79056b26d051 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlidersComponent.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.Modifier import com.android.compose.PlatformSliderDefaults import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel import com.android.systemui.volume.panel.component.volume.ui.viewmodel.AudioVolumeComponentViewModel +import com.android.systemui.volume.panel.component.volume.ui.viewmodel.SlidersExpandableViewModel import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope import com.android.systemui.volume.panel.ui.composable.isPortrait @@ -48,13 +49,21 @@ constructor( modifier = modifier.fillMaxWidth(), ) } else { - val isExpanded by viewModel.isExpanded.collectAsState() + val expandableViewModel: SlidersExpandableViewModel by + viewModel + .isExpandable(isPortrait) + .collectAsState(SlidersExpandableViewModel.Unavailable) + if (expandableViewModel is SlidersExpandableViewModel.Unavailable) { + return + } + val isExpanded = + (expandableViewModel as? SlidersExpandableViewModel.Expandable)?.isExpanded ?: true ColumnVolumeSliders( viewModels = sliderViewModels, isExpanded = isExpanded, onExpandedChanged = viewModel::onExpandedChanged, sliderColors = PlatformSliderDefaults.defaultPlatformSliderColors(), - isExpandable = isPortrait, + isExpandable = expandableViewModel is SlidersExpandableViewModel.Expandable, modifier = modifier.fillMaxWidth(), ) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt index d924d88d1e59..92d5c26148e4 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt @@ -74,6 +74,16 @@ sealed interface ObservableTransitionState { */ val isUserInputOngoing: Flow<Boolean>, ) : ObservableTransitionState + + fun isIdle(scene: SceneKey?): Boolean { + return this is Idle && (scene == null || this.currentScene == scene) + } + + fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean { + return this is Transition && + (from == null || this.fromScene == from) && + (to == null || this.toScene == to) + } } /** 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 f539a23d9cb0..bdeab797d165 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 @@ -28,16 +28,16 @@ import android.text.format.DateFormat import android.util.AttributeSet import android.util.MathUtils.constrainedMap import android.util.TypedValue -import android.view.View.MeasureSpec.EXACTLY import android.view.View +import android.view.View.MeasureSpec.EXACTLY import android.widget.TextView import com.android.app.animation.Interpolators import com.android.internal.annotations.VisibleForTesting import com.android.systemui.animation.GlyphCallback import com.android.systemui.animation.TextAnimator import com.android.systemui.customization.R -import com.android.systemui.log.core.LogcatOnlyMessageBuffer 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 java.io.PrintWriter @@ -47,11 +47,13 @@ import java.util.TimeZone import kotlin.math.min /** - * Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30) - * The time's text color is a gradient that changes its colors based on its controller. + * Displays the time with the hour positioned above the minutes (ie: 09 above 30 is 9:30). The + * time's text color is a gradient that changes its colors based on its controller. */ @SuppressLint("AppCompatCustomView") -class AnimatableClockView @JvmOverloads constructor( +class AnimatableClockView +@JvmOverloads +constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, @@ -63,7 +65,9 @@ class AnimatableClockView @JvmOverloads constructor( get() = field ?: DEFAULT_LOGGER var messageBuffer: MessageBuffer get() = logger.buffer - set(value) { logger = Logger(value, TAG) } + set(value) { + logger = Logger(value, TAG) + } var hasCustomPositionUpdatedAnimation: Boolean = false var migratedClocks: Boolean = false @@ -77,16 +81,13 @@ class AnimatableClockView @JvmOverloads constructor( private var format: CharSequence? = null private var descFormat: CharSequence? = null - @ColorInt - private var dozingColor = 0 - - @ColorInt - private var lockScreenColor = 0 + @ColorInt private var dozingColor = 0 + @ColorInt private var lockScreenColor = 0 private var lineSpacingScale = 1f private val chargeAnimationDelay: Int private var textAnimator: TextAnimator? = null - private var onTextAnimatorInitialized: Runnable? = null + private var onTextAnimatorInitialized: ((TextAnimator) -> Unit)? = null private var translateForCenterAnimation = false private val parentWidth: Int @@ -94,9 +95,11 @@ class AnimatableClockView @JvmOverloads constructor( // last text size which is not constrained by view height private var lastUnconstrainedTextSize: Float = Float.MAX_VALUE - @VisibleForTesting var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator = - { layout, invalidateCb -> - TextAnimator(layout, NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb) } + + @VisibleForTesting + var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator = { layout, invalidateCb -> + TextAnimator(layout, NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb) + } // Used by screenshot tests to provide stability @VisibleForTesting var isAnimationEnabled: Boolean = true @@ -109,40 +112,55 @@ class AnimatableClockView @JvmOverloads constructor( get() = if (useBoldedVersion()) lockScreenWeightInternal + 100 else lockScreenWeightInternal /** - * The number of pixels below the baseline. For fonts that support languages such as - * Burmese, this space can be significant and should be accounted for when computing layout. + * The number of pixels below the baseline. For fonts that support languages such as Burmese, + * this space can be significant and should be accounted for when computing layout. */ - val bottom get() = paint?.fontMetrics?.bottom ?: 0f + val bottom: Float + get() = paint?.fontMetrics?.bottom ?: 0f init { - val animatableClockViewAttributes = context.obtainStyledAttributes( - attrs, R.styleable.AnimatableClockView, defStyleAttr, defStyleRes - ) + val animatableClockViewAttributes = + context.obtainStyledAttributes( + attrs, + R.styleable.AnimatableClockView, + defStyleAttr, + defStyleRes + ) try { - dozingWeightInternal = animatableClockViewAttributes.getInt( - R.styleable.AnimatableClockView_dozeWeight, - /* default = */ 100 - ) - lockScreenWeightInternal = animatableClockViewAttributes.getInt( - R.styleable.AnimatableClockView_lockScreenWeight, - /* default = */ 300 - ) - chargeAnimationDelay = animatableClockViewAttributes.getInt( - R.styleable.AnimatableClockView_chargeAnimationDelay, /* default = */ 200 - ) + dozingWeightInternal = + animatableClockViewAttributes.getInt( + R.styleable.AnimatableClockView_dozeWeight, + /* default = */ 100 + ) + lockScreenWeightInternal = + animatableClockViewAttributes.getInt( + R.styleable.AnimatableClockView_lockScreenWeight, + /* default = */ 300 + ) + chargeAnimationDelay = + animatableClockViewAttributes.getInt( + R.styleable.AnimatableClockView_chargeAnimationDelay, + /* default = */ 200 + ) } finally { animatableClockViewAttributes.recycle() } - val textViewAttributes = context.obtainStyledAttributes( - attrs, android.R.styleable.TextView, - defStyleAttr, defStyleRes - ) + val textViewAttributes = + context.obtainStyledAttributes( + attrs, + android.R.styleable.TextView, + defStyleAttr, + defStyleRes + ) try { - isSingleLineInternal = textViewAttributes.getBoolean( - android.R.styleable.TextView_singleLine, /* default = */ false) + isSingleLineInternal = + textViewAttributes.getBoolean( + android.R.styleable.TextView_singleLine, + /* default = */ false + ) } finally { textViewAttributes.recycle() } @@ -156,9 +174,7 @@ class AnimatableClockView @JvmOverloads constructor( refreshFormat() } - /** - * Whether to use a bolded version based on the user specified fontWeightAdjustment. - */ + /** Whether to use a bolded version based on the user specified fontWeightAdjustment. */ fun useBoldedVersion(): Boolean { // "Bold text" fontWeightAdjustment is 300. return resources.configuration.fontWeightAdjustment > 100 @@ -169,25 +185,30 @@ class AnimatableClockView @JvmOverloads constructor( contentDescription = DateFormat.format(descFormat, time) val formattedText = DateFormat.format(format, time) logger.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() } - // Setting text actually triggers a layout pass (because the text view is set to - // wrap_content width and TextView always relayouts for this). Avoid needless - // relayout if the text didn't actually change. - if (!TextUtils.equals(text, formattedText)) { - text = formattedText - logger.d({ "refreshTime: done setting new time text to: $str1" }) { - str1 = formattedText?.toString() - } - // Because the TextLayout may mutate under the hood as a result of the new text, we - // notify the TextAnimator that it may have changed and request a measure/layout. A - // crash will occur on the next invocation of setTextStyle if the layout is mutated - // without being notified TextInterpolator being notified. - if (layout != null) { - textAnimator?.updateLayout(layout) - logger.d("refreshTime: done updating textAnimator layout") - } - requestLayout() - logger.d("refreshTime: after requestLayout") + + // 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 + // if the text didn't actually change. + if (TextUtils.equals(text, formattedText)) { + return } + + text = formattedText + logger.d({ "refreshTime: done setting new time text to: $str1" }) { + str1 = formattedText?.toString() + } + + // Because the TextLayout may mutate under the hood as a result of the new text, we notify + // the TextAnimator that it may have changed and request a measure/layout. A crash will + // occur on the next invocation of setTextStyle if the layout is mutated without being + // notified TextInterpolator being notified. + if (layout != null) { + textAnimator?.updateLayout(layout) + logger.d("refreshTime: done updating textAnimator layout") + } + + requestLayout() + logger.d("refreshTime: after requestLayout") } fun onTimeZoneChanged(timeZone: TimeZone?) { @@ -206,19 +227,27 @@ class AnimatableClockView @JvmOverloads constructor( @SuppressLint("DrawAllocation") override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { logger.d("onMeasure") - if (migratedClocks && !isSingleLineInternal && - MeasureSpec.getMode(heightMeasureSpec) == EXACTLY) { + + if ( + migratedClocks && + !isSingleLineInternal && + MeasureSpec.getMode(heightMeasureSpec) == EXACTLY + ) { // Call straight into TextView.setTextSize to avoid setting lastUnconstrainedTextSize - super.setTextSize(TypedValue.COMPLEX_UNIT_PX, - min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F)) + super.setTextSize( + TypedValue.COMPLEX_UNIT_PX, + min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F) + ) } super.onMeasure(widthMeasureSpec, heightMeasureSpec) val animator = textAnimator if (animator == null) { - textAnimator = textAnimatorFactory(layout, ::invalidate) - onTextAnimatorInitialized?.run() - onTextAnimatorInitialized = null + textAnimator = + textAnimatorFactory(layout, ::invalidate)?.also { + onTextAnimatorInitialized?.invoke(it) + onTextAnimatorInitialized = null + } } else { animator.updateLayout(layout) } @@ -243,15 +272,13 @@ class AnimatableClockView @JvmOverloads constructor( canvas.translate(parentWidth / 4f, 0f) } - logger.d({ "onDraw($str1)"}) { str1 = text.toString() } + logger.d({ "onDraw($str1)" }) { str1 = text.toString() } // intentionally doesn't call super.onDraw here or else the text will be rendered twice textAnimator?.draw(canvas) canvas.restore() } override fun invalidate() { - @Suppress("UNNECESSARY_SAFE_CALL") - // logger won't be initialized when called by TextView's constructor logger.d("invalidate") super.invalidate() } @@ -325,6 +352,7 @@ class AnimatableClockView @JvmOverloads constructor( if (textAnimator == null) { return } + logger.d("animateFoldAppear") setTextStyle( weight = lockScreenWeightInternal, @@ -348,10 +376,11 @@ class AnimatableClockView @JvmOverloads constructor( } fun animateCharge(isDozing: () -> Boolean) { + // Skip charge animation if dozing animation is already playing. if (textAnimator == null || textAnimator!!.isRunning()) { - // Skip charge animation if dozing animation is already playing. return } + logger.d("animateCharge") val startAnimPhase2 = Runnable { setTextStyle( @@ -409,10 +438,9 @@ class AnimatableClockView @JvmOverloads constructor( /** * Set text style with an optional animation. - * - * By passing -1 to weight, the view preserves its current weight. - * By passing -1 to textSize, the view preserves its current text size. - * By passing null to color, the view preserves its current color. + * - By passing -1 to weight, the view preserves its current weight. + * - By passing -1 to textSize, the view preserves its current text size. + * - By passing null to color, the view preserves its current color. * * @param weight text weight. * @param textSize font size. @@ -428,8 +456,8 @@ class AnimatableClockView @JvmOverloads constructor( delay: Long, onAnimationEnd: Runnable? ) { - if (textAnimator != null) { - textAnimator?.setTextStyle( + textAnimator?.let { + it.setTextStyle( weight = weight, textSize = textSize, color = color, @@ -439,23 +467,24 @@ class AnimatableClockView @JvmOverloads constructor( delay = delay, onAnimationEnd = onAnimationEnd ) - textAnimator?.glyphFilter = glyphFilter - } else { - // when the text animator is set, update its start values - onTextAnimatorInitialized = Runnable { - textAnimator?.setTextStyle( - weight = weight, - textSize = textSize, - color = color, - animate = false, - duration = duration, - interpolator = interpolator, - delay = delay, - onAnimationEnd = onAnimationEnd - ) - textAnimator?.glyphFilter = glyphFilter - } + it.glyphFilter = glyphFilter } + ?: run { + // when the text animator is set, update its start values + onTextAnimatorInitialized = { textAnimator -> + textAnimator.setTextStyle( + weight = weight, + textSize = textSize, + color = color, + animate = false, + duration = duration, + interpolator = interpolator, + delay = delay, + onAnimationEnd = onAnimationEnd + ) + textAnimator.glyphFilter = glyphFilter + } + } } private fun setTextStyle( @@ -483,12 +512,13 @@ class AnimatableClockView @JvmOverloads constructor( fun refreshFormat(use24HourFormat: Boolean) { Patterns.update(context) - format = when { - isSingleLineInternal && use24HourFormat -> Patterns.sClockView24 - !isSingleLineInternal && use24HourFormat -> DOUBLE_LINE_FORMAT_24_HOUR - isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12 - else -> DOUBLE_LINE_FORMAT_12_HOUR - } + format = + when { + isSingleLineInternal && use24HourFormat -> Patterns.sClockView24 + !isSingleLineInternal && use24HourFormat -> DOUBLE_LINE_FORMAT_24_HOUR + isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12 + else -> DOUBLE_LINE_FORMAT_12_HOUR + } logger.d({ "refreshFormat($str1)" }) { str1 = format?.toString() } descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12 @@ -510,10 +540,10 @@ class AnimatableClockView @JvmOverloads constructor( pw.println(" time=$time") } - private val moveToCenterDelays + private val moveToCenterDelays: List<Int> get() = if (isLayoutRtl) MOVE_LEFT_DELAYS else MOVE_RIGHT_DELAYS - private val moveToSideDelays + private val moveToSideDelays: List<Int> get() = if (isLayoutRtl) MOVE_RIGHT_DELAYS else MOVE_LEFT_DELAYS /** @@ -531,7 +561,7 @@ class AnimatableClockView @JvmOverloads constructor( fun offsetGlyphsForStepClockAnimation( clockStartLeft: Int, clockMoveDirection: Int, - moveFraction: Float + moveFraction: Float, ) { val isMovingToCenter = if (isLayoutRtl) clockMoveDirection < 0 else clockMoveDirection > 0 val currentMoveAmount = left - clockStartLeft @@ -558,8 +588,8 @@ class AnimatableClockView @JvmOverloads constructor( * * @param distance is the total distance in pixels to offset the glyphs when animation * completes. Negative distance means we are animating the position towards the center. - * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 - * means it finished moving. + * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means + * it finished moving. */ fun offsetGlyphsForStepClockAnimation( distance: Float, @@ -568,13 +598,17 @@ class AnimatableClockView @JvmOverloads constructor( for (i in 0 until NUM_DIGITS) { val dir = if (isLayoutRtl) -1 else 1 val digitFraction = - getDigitFraction(digit = i, isMovingToCenter = distance > 0, fraction = fraction) + getDigitFraction( + digit = i, + isMovingToCenter = distance > 0, + fraction = fraction, + ) val moveAmountForDigit = dir * distance * digitFraction glyphOffsets[i] = moveAmountForDigit if (distance > 0) { - // If distance > 0 then we are moving from the left towards the center. - // We need ensure that the glyphs are offset to the initial position. + // If distance > 0 then we are moving from the left towards the center. We need to + // ensure that the glyphs are offset to the initial position. glyphOffsets[i] -= dir * distance } } @@ -582,27 +616,25 @@ class AnimatableClockView @JvmOverloads constructor( } private fun getDigitFraction(digit: Int, isMovingToCenter: Boolean, fraction: Float): Float { - // The delay for the digit, in terms of fraction (i.e. the digit should not move - // during 0.0 - 0.1). - val digitInitialDelay = - if (isMovingToCenter) { - moveToCenterDelays[digit] * MOVE_DIGIT_STEP - } else { - moveToSideDelays[digit] * MOVE_DIGIT_STEP - } + // The delay for the digit, in terms of fraction. + // (i.e. the digit should not move during 0.0 - 0.1). + val delays = if (isMovingToCenter) moveToCenterDelays else moveToSideDelays + val digitInitialDelay = delays[digit] * MOVE_DIGIT_STEP return MOVE_INTERPOLATOR.getInterpolation( - constrainedMap( - 0.0f, - 1.0f, - digitInitialDelay, - digitInitialDelay + AVAILABLE_ANIMATION_TIME, - fraction, - ) + constrainedMap( + /* rangeMin= */ 0.0f, + /* rangeMax= */ 1.0f, + /* valueMin= */ digitInitialDelay, + /* valueMax= */ digitInitialDelay + AVAILABLE_ANIMATION_TIME, + /* value= */ fraction, ) + ) } - // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often. - // This is an optimization to ensure we only recompute the patterns when the inputs change. + /** + * DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often. This + * is a cache optimization to ensure we only recompute the patterns when the inputs change. + */ private object Patterns { var sClockView12: String? = null var sClockView24: String? = null @@ -610,21 +642,22 @@ class AnimatableClockView @JvmOverloads constructor( fun update(context: Context) { val locale = Locale.getDefault() - val res = context.resources - val clockView12Skel = res.getString(R.string.clock_12hr_format) - val clockView24Skel = res.getString(R.string.clock_24hr_format) - val key = locale.toString() + clockView12Skel + clockView24Skel - if (key == sCacheKey) return - - val clockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel) - sClockView12 = clockView12 - - // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton - // format. The following code removes the AM/PM indicator if we didn't want it. - if (!clockView12Skel.contains("a")) { - sClockView12 = clockView12.replace("a".toRegex(), "").trim { it <= ' ' } + val clockView12Skel = context.resources.getString(R.string.clock_12hr_format) + val clockView24Skel = context.resources.getString(R.string.clock_24hr_format) + val key = "$locale$clockView12Skel$clockView24Skel" + if (key == sCacheKey) { + return } + sClockView12 = + DateFormat.getBestDateTimePattern(locale, clockView12Skel).let { + // CLDR insists on adding an AM/PM indicator even though it wasn't in the format + // string. The following code removes the AM/PM indicator if we didn't want it. + if (!clockView12Skel.contains("a")) { + it.replace("a".toRegex(), "").trim { it <= ' ' } + } else it + } + sClockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel) sCacheKey = key } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt index 3d8159e70061..9c9ee53d9c56 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt @@ -24,7 +24,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.util.settings.FakeSettings -import com.google.common.truth.Truth import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher @@ -66,7 +65,7 @@ class ColorInversionRepositoryImplTest : SysuiTestCase() { runCurrent() - Truth.assertThat(actualValue).isFalse() + assertThat(actualValue).isFalse() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt new file mode 100644 index 000000000000..ca824cbdd53b --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.data.repository + +import android.hardware.display.ColorDisplayManager +import android.hardware.display.NightDisplayListener +import android.os.UserHandle +import android.provider.Settings +import android.testing.LeakCheck +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.dagger.NightDisplayListenerModule +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.user.utils.UserScopedService +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.settings.fakeGlobalSettings +import com.android.systemui.util.settings.fakeSettings +import com.android.systemui.utils.leaks.FakeLocationController +import com.google.common.truth.Truth.assertThat +import java.time.LocalTime +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers +import org.mockito.Mockito.verify + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class NightDisplayRepositoryTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val testUser = UserHandle.of(1)!! + private val testStartTime = LocalTime.MIDNIGHT + private val testEndTime = LocalTime.NOON + private val colorDisplayManager = + mock<ColorDisplayManager> { + whenever(nightDisplayAutoMode).thenReturn(ColorDisplayManager.AUTO_MODE_DISABLED) + whenever(isNightDisplayActivated).thenReturn(false) + whenever(nightDisplayCustomStartTime).thenReturn(testStartTime) + whenever(nightDisplayCustomEndTime).thenReturn(testEndTime) + } + private val locationController = FakeLocationController(LeakCheck()) + private val nightDisplayListener = mock<NightDisplayListener>() + private val listenerBuilder = + mock<NightDisplayListenerModule.Builder> { + whenever(setUser(ArgumentMatchers.anyInt())).thenReturn(this) + whenever(build()).thenReturn(nightDisplayListener) + } + private val globalSettings = kosmos.fakeGlobalSettings + private val secureSettings = kosmos.fakeSettings + private val testDispatcher = StandardTestDispatcher() + private val scope = TestScope(testDispatcher) + private val userScopedColorDisplayManager = + mock<UserScopedService<ColorDisplayManager>> { + whenever(forUser(eq(testUser))).thenReturn(colorDisplayManager) + } + + private val underTest = + NightDisplayRepository( + testDispatcher, + scope.backgroundScope, + globalSettings, + secureSettings, + listenerBuilder, + userScopedColorDisplayManager, + locationController, + ) + + @Before + fun setup() { + enrollInForcedNightDisplayAutoMode(INITIALLY_FORCE_AUTO_MODE, testUser) + } + + @Test + fun nightDisplayState_matchesAutoMode() = + scope.runTest { + enrollInForcedNightDisplayAutoMode(INITIALLY_FORCE_AUTO_MODE, testUser) + val callbackCaptor = argumentCaptor<NightDisplayListener.Callback>() + val lastState by collectLastValue(underTest.nightDisplayState(testUser)) + + runCurrent() + + verify(nightDisplayListener).setCallback(callbackCaptor.capture()) + val callback = callbackCaptor.value + + assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_DISABLED) + + callback.onAutoModeChanged(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME) + assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME) + + callback.onCustomStartTimeChanged(testStartTime) + assertThat(lastState!!.startTime).isEqualTo(testStartTime) + + callback.onCustomEndTimeChanged(testEndTime) + assertThat(lastState!!.endTime).isEqualTo(testEndTime) + + callback.onAutoModeChanged(ColorDisplayManager.AUTO_MODE_TWILIGHT) + + assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_TWILIGHT) + } + + @Test + fun nightDisplayState_matchesIsNightDisplayActivated() = + scope.runTest { + val callbackCaptor = argumentCaptor<NightDisplayListener.Callback>() + + val lastState by collectLastValue(underTest.nightDisplayState(testUser)) + runCurrent() + + verify(nightDisplayListener).setCallback(callbackCaptor.capture()) + val callback = callbackCaptor.value + assertThat(lastState!!.isActivated) + .isEqualTo(colorDisplayManager.isNightDisplayActivated) + + callback.onActivated(true) + assertThat(lastState!!.isActivated).isTrue() + + callback.onActivated(false) + assertThat(lastState!!.isActivated).isFalse() + } + + @Test + fun nightDisplayState_matchesController_initiallyCustomAutoMode() = + scope.runTest { + whenever(colorDisplayManager.nightDisplayAutoMode) + .thenReturn(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME) + + val lastState by collectLastValue(underTest.nightDisplayState(testUser)) + runCurrent() + + assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME) + } + + @Test + fun nightDisplayState_matchesController_initiallyTwilightAutoMode() = + scope.runTest { + whenever(colorDisplayManager.nightDisplayAutoMode) + .thenReturn(ColorDisplayManager.AUTO_MODE_TWILIGHT) + + val lastState by collectLastValue(underTest.nightDisplayState(testUser)) + runCurrent() + + assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_TWILIGHT) + } + + @Test + fun nightDisplayState_matchesForceAutoMode() = + scope.runTest { + enrollInForcedNightDisplayAutoMode(false, testUser) + val lastState by collectLastValue(underTest.nightDisplayState(testUser)) + runCurrent() + + assertThat(lastState!!.shouldForceAutoMode).isEqualTo(false) + + enrollInForcedNightDisplayAutoMode(true, testUser) + assertThat(lastState!!.shouldForceAutoMode).isEqualTo(true) + } + + private fun enrollInForcedNightDisplayAutoMode(enroll: Boolean, userHandle: UserHandle) { + globalSettings.putString( + Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE, + if (enroll) NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE + else NIGHT_DISPLAY_FORCED_AUTO_MODE_UNAVAILABLE + ) + secureSettings.putIntForUser( + Settings.Secure.NIGHT_DISPLAY_AUTO_MODE, + if (enroll) NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET else NIGHT_DISPLAY_AUTO_MODE_RAW_SET, + userHandle.identifier + ) + } + + private companion object { + const val INITIALLY_FORCE_AUTO_MODE = false + const val NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE = "1" + const val NIGHT_DISPLAY_FORCED_AUTO_MODE_UNAVAILABLE = "0" + const val NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET = -1 + const val NIGHT_DISPLAY_AUTO_MODE_RAW_SET = 0 + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java index f4ad7649a18f..9b1d4eca8b2b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -84,7 +84,6 @@ import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.domain.interactor.LogContextInteractor; -import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor; import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor; import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel; import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel; @@ -161,8 +160,6 @@ public class AuthControllerTest extends SysuiTestCase { @Mock private InteractionJankMonitor mInteractionJankMonitor; @Mock - private PromptCredentialInteractor mBiometricPromptCredentialInteractor; - @Mock private PromptSelectorInteractor mPromptSelectionInteractor; @Mock private CredentialViewModel mCredentialViewModel; @@ -1057,7 +1054,6 @@ public class AuthControllerTest extends SysuiTestCase { private final class TestableAuthController extends AuthController { private int mBuildCount = 0; - private PromptInfo mLastBiometricPromptInfo; TestableAuthController(Context context) { super(context, null /* applicationCoroutineScope */, @@ -1065,8 +1061,8 @@ public class AuthControllerTest extends SysuiTestCase { mFingerprintManager, mFaceManager, () -> mUdfpsController, mDisplayManager, mWakefulnessLifecycle, mPanelInteractionDetector, mUserManager, mLockPatternUtils, () -> mUdfpsLogger, () -> mLogContextInteractor, - () -> mBiometricPromptCredentialInteractor, () -> mPromptSelectionInteractor, - () -> mCredentialViewModel, () -> mPromptViewModel, mInteractionJankMonitor, + () -> mPromptSelectionInteractor, () -> mCredentialViewModel, + () -> mPromptViewModel, mInteractionJankMonitor, mHandler, mBackgroundExecutor, mUdfpsUtils, mVibratorHelper); } @@ -1079,8 +1075,6 @@ public class AuthControllerTest extends SysuiTestCase { UserManager userManager, LockPatternUtils lockPatternUtils, PromptViewModel viewModel) { - mLastBiometricPromptInfo = promptInfo; - AuthDialog dialog; if (mBuildCount == 0) { dialog = mDialog1; diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageChangeRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageChangeRepositoryTest.kt index 23869576a7a4..7628debf245a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageChangeRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageChangeRepositoryTest.kt @@ -50,6 +50,7 @@ class PackageChangeRepositoryTest : SysuiTestCase() { @Mock private lateinit var context: Context @Mock private lateinit var packageManager: PackageManager @Mock private lateinit var handler: Handler + @Mock private lateinit var packageInstallerMonitor: PackageInstallerMonitor private lateinit var repository: PackageChangeRepository private lateinit var updateMonitor: PackageUpdateMonitor @@ -60,19 +61,20 @@ class PackageChangeRepositoryTest : SysuiTestCase() { MockitoAnnotations.initMocks(this@PackageChangeRepositoryTest) whenever(context.packageManager).thenReturn(packageManager) - repository = PackageChangeRepositoryImpl { user -> - updateMonitor = - PackageUpdateMonitor( - user = user, - bgDispatcher = testDispatcher, - scope = applicationCoroutineScope, - context = context, - bgHandler = handler, - logger = PackageUpdateLogger(logcatLogBuffer()), - systemClock = fakeSystemClock, - ) - updateMonitor - } + repository = + PackageChangeRepositoryImpl(packageInstallerMonitor) { user -> + updateMonitor = + PackageUpdateMonitor( + user = user, + bgDispatcher = testDispatcher, + scope = applicationCoroutineScope, + context = context, + bgHandler = handler, + logger = PackageUpdateLogger(logcatLogBuffer()), + systemClock = fakeSystemClock, + ) + updateMonitor + } } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt new file mode 100644 index 000000000000..5556b04c2d20 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageInstallerMonitorTest.kt @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.common.data.repository + +import android.content.pm.PackageInstaller +import android.content.pm.PackageInstaller.SessionInfo +import android.graphics.Bitmap +import android.os.fakeExecutorHandler +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.PackageInstallSession +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testScope +import com.android.systemui.log.logcatLogBuffer +import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.mockito.withArgCaptor +import com.google.common.truth.Correspondence +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class PackageInstallerMonitorTest : SysuiTestCase() { + @Mock private lateinit var packageInstaller: PackageInstaller + @Mock private lateinit var icon1: Bitmap + @Mock private lateinit var icon2: Bitmap + @Mock private lateinit var icon3: Bitmap + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private val handler = kosmos.fakeExecutorHandler + + private lateinit var session1: SessionInfo + private lateinit var session2: SessionInfo + private lateinit var session3: SessionInfo + + private lateinit var defaultSessions: List<SessionInfo> + + private lateinit var underTest: PackageInstallerMonitor + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + + session1 = + SessionInfo().apply { + sessionId = 1 + appPackageName = "pkg_name_1" + appIcon = icon1 + } + session2 = + SessionInfo().apply { + sessionId = 2 + appPackageName = "pkg_name_2" + appIcon = icon2 + } + session3 = + SessionInfo().apply { + sessionId = 3 + appPackageName = "pkg_name_3" + appIcon = icon3 + } + defaultSessions = listOf(session1, session2) + + whenever(packageInstaller.allSessions).thenReturn(defaultSessions) + whenever(packageInstaller.getSessionInfo(1)).thenReturn(session1) + whenever(packageInstaller.getSessionInfo(2)).thenReturn(session2) + + underTest = + PackageInstallerMonitor( + handler, + kosmos.applicationCoroutineScope, + logcatLogBuffer("PackageInstallerRepositoryImplTest"), + packageInstaller, + ) + } + + @Test + fun installSessions_callbacksRegisteredOnlyWhenFlowIsCollected() = + testScope.runTest { + // Verify callback not added before flow is collected + verify(packageInstaller, never()).registerSessionCallback(any(), eq(handler)) + + // Start collecting the flow + val job = + backgroundScope.launch { + underTest.installSessionsForPrimaryUser.collect { + // Do nothing with the value + } + } + runCurrent() + + // Verify callback added only after flow is collected + val callback = + withArgCaptor<PackageInstaller.SessionCallback> { + verify(packageInstaller).registerSessionCallback(capture(), eq(handler)) + } + + // Verify callback not removed + verify(packageInstaller, never()).unregisterSessionCallback(any()) + + // Stop collecting the flow + job.cancel() + runCurrent() + + // Verify callback removed once flow stops being collected + verify(packageInstaller).unregisterSessionCallback(eq(callback)) + } + + @Test + fun installSessions_newSessionsAreAdded() = + testScope.runTest { + val installSessions by collectLastValue(underTest.installSessionsForPrimaryUser) + assertThat(installSessions) + .comparingElementsUsing(represents) + .containsExactlyElementsIn(defaultSessions) + + val callback = + withArgCaptor<PackageInstaller.SessionCallback> { + verify(packageInstaller).registerSessionCallback(capture(), eq(handler)) + } + + // New session added + whenever(packageInstaller.getSessionInfo(3)).thenReturn(session3) + callback.onCreated(3) + runCurrent() + + // Verify flow updated with the new session + assertThat(installSessions) + .comparingElementsUsing(represents) + .containsExactlyElementsIn(defaultSessions + session3) + } + + @Test + fun installSessions_finishedSessionsAreRemoved() = + testScope.runTest { + val installSessions by collectLastValue(underTest.installSessionsForPrimaryUser) + assertThat(installSessions) + .comparingElementsUsing(represents) + .containsExactlyElementsIn(defaultSessions) + + val callback = + withArgCaptor<PackageInstaller.SessionCallback> { + verify(packageInstaller).registerSessionCallback(capture(), eq(handler)) + } + + // Session 1 finished successfully + callback.onFinished(1, /* success = */ true) + runCurrent() + + // Verify flow updated with session 1 removed + assertThat(installSessions) + .comparingElementsUsing(represents) + .containsExactlyElementsIn(defaultSessions - session1) + } + + @Test + fun installSessions_sessionsUpdatedOnBadgingChange() = + testScope.runTest { + val installSessions by collectLastValue(underTest.installSessionsForPrimaryUser) + assertThat(installSessions) + .comparingElementsUsing(represents) + .containsExactlyElementsIn(defaultSessions) + + val callback = + withArgCaptor<PackageInstaller.SessionCallback> { + verify(packageInstaller).registerSessionCallback(capture(), eq(handler)) + } + + // App icon for session 1 updated + val newSession = + SessionInfo().apply { + sessionId = 1 + appPackageName = "pkg_name_1" + appIcon = mock() + } + whenever(packageInstaller.getSessionInfo(1)).thenReturn(newSession) + callback.onBadgingChanged(1) + runCurrent() + + // Verify flow updated with the new session 1 + assertThat(installSessions) + .comparingElementsUsing(represents) + .containsExactlyElementsIn(defaultSessions - session1 + newSession) + } + + private val represents = + Correspondence.from<PackageInstallSession, SessionInfo>( + { actual, expected -> + actual?.sessionId == expected?.sessionId && + actual?.packageName == expected?.appPackageName && + actual?.icon == expected?.getAppIcon() + }, + "represents", + ) +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt index def63ec5b171..bfed33c54019 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt @@ -16,12 +16,16 @@ package com.android.systemui.communal +import android.platform.test.annotations.EnableFlags import android.service.dream.dreamManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.keyguardRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState @@ -62,6 +66,7 @@ class CommunalDreamStartableTest : SysuiTestCase() { powerInteractor = kosmos.powerInteractor, keyguardInteractor = kosmos.keyguardInteractor, keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor, + communalInteractor = kosmos.communalInteractor, dreamManager = dreamManager, bgScope = kosmos.applicationCoroutineScope, ) @@ -84,6 +89,32 @@ class CommunalDreamStartableTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE) + fun restartDreamingWhenTransitioningFromDreamingToOccludedToDreaming() = + testScope.runTest { + keyguardRepository.setDreaming(false) + powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON) + whenever(dreamManager.canStartDreaming(/* isScreenOn = */ true)).thenReturn(true) + runCurrent() + + verify(dreamManager, never()).startDream() + + kosmos.fakeKeyguardRepository.setKeyguardOccluded(true) + kosmos.fakeKeyguardRepository.setDreaming(true) + runCurrent() + + transition(from = KeyguardState.DREAMING, to = KeyguardState.OCCLUDED) + kosmos.fakeKeyguardRepository.setKeyguardOccluded(false) + kosmos.fakeKeyguardRepository.setDreaming(false) + runCurrent() + + transition(from = KeyguardState.OCCLUDED, to = KeyguardState.DREAMING) + runCurrent() + + verify(dreamManager).startDream() + } + + @Test fun shouldNotStartDreamWhenIneligibleToDream() = testScope.runTest { keyguardRepository.setDreaming(false) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt index 1cdc2b69034b..407bf4cac633 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt @@ -114,7 +114,7 @@ class CommunalMediaRepositoryImplTest : SysuiTestCase() { // Change to media unavailable and notify the listener. whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false) - mediaDataListenerCaptor.value.onMediaDataRemoved("key") + mediaDataListenerCaptor.value.onMediaDataRemoved("key", false) runCurrent() // Media active now returns false. diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt index fe4d32d88612..6ce6cdb32a12 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt @@ -17,16 +17,18 @@ package com.android.systemui.communal.data.repository import android.app.backup.BackupManager -import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProviderInfo import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE import android.content.ComponentName import android.content.applicationContext +import android.graphics.Bitmap import android.os.UserHandle import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.common.data.repository.fakePackageChangeRepository +import com.android.systemui.common.shared.model.PackageInstallSession import com.android.systemui.communal.data.backup.CommunalBackupUtils import com.android.systemui.communal.data.db.CommunalItemRank import com.android.systemui.communal.data.db.CommunalWidgetDao @@ -46,10 +48,10 @@ import com.android.systemui.log.logcatLogBuffer import com.android.systemui.res.R 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.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat -import java.util.Optional import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runCurrent @@ -58,7 +60,6 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.anyInt import org.mockito.Mockito.eq import org.mockito.Mockito.never import org.mockito.Mockito.verify @@ -68,10 +69,10 @@ import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidJUnit4::class) class CommunalWidgetRepositoryImplTest : SysuiTestCase() { - @Mock private lateinit var appWidgetManager: AppWidgetManager @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost - @Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo @Mock private lateinit var providerInfoA: AppWidgetProviderInfo + @Mock private lateinit var providerInfoB: AppWidgetProviderInfo + @Mock private lateinit var providerInfoC: AppWidgetProviderInfo @Mock private lateinit var communalWidgetHost: CommunalWidgetHost @Mock private lateinit var communalWidgetDao: CommunalWidgetDao @Mock private lateinit var backupManager: BackupManager @@ -79,9 +80,11 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { private lateinit var backupUtils: CommunalBackupUtils private lateinit var logBuffer: LogBuffer private lateinit var fakeWidgets: MutableStateFlow<Map<CommunalItemRank, CommunalWidgetItem>> + private lateinit var fakeProviders: MutableStateFlow<Map<Int, AppWidgetProviderInfo?>> private val kosmos = testKosmos() private val testScope = kosmos.testScope + private val packageChangeRepository = kosmos.fakePackageChangeRepository private val fakeAllowlist = listOf( @@ -96,6 +99,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) fakeWidgets = MutableStateFlow(emptyMap()) + fakeProviders = MutableStateFlow(emptyMap()) logBuffer = logcatLogBuffer(name = "CommunalWidgetRepoImplTest") backupUtils = CommunalBackupUtils(kosmos.applicationContext) @@ -103,12 +107,11 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray()) - whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch") whenever(communalWidgetDao.getWidgets()).thenReturn(fakeWidgets) + whenever(communalWidgetHost.appWidgetProviders).thenReturn(fakeProviders) underTest = CommunalWidgetRepositoryImpl( - Optional.of(appWidgetManager), appWidgetHost, testScope.backgroundScope, kosmos.testDispatcher, @@ -117,6 +120,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { logBuffer, backupManager, backupUtils, + packageChangeRepository, ) } @@ -126,15 +130,13 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1) val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L) fakeWidgets.value = mapOf(communalItemRankEntry to communalWidgetItemEntry) - whenever(appWidgetManager.getAppWidgetInfo(anyInt())).thenReturn(providerInfoA) - - installedProviders(listOf(stopwatchProviderInfo)) + fakeProviders.value = mapOf(1 to providerInfoA) val communalWidgets by collectLastValue(underTest.communalWidgets) verify(communalWidgetDao).getWidgets() assertThat(communalWidgets) .containsExactly( - CommunalWidgetContentModel( + CommunalWidgetContentModel.Available( appWidgetId = communalWidgetItemEntry.widgetId, providerInfo = providerInfoA, priority = communalItemRankEntry.rank, @@ -146,6 +148,102 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { } @Test + fun communalWidgets_widgetsWithoutMatchingProvidersAreSkipped() = + testScope.runTest { + // Set up 4 widgets, but widget 3 and 4 don't have matching providers + fakeWidgets.value = + mapOf( + CommunalItemRank(uid = 1L, rank = 1) to + CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L), + CommunalItemRank(uid = 2L, rank = 2) to + CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L), + CommunalItemRank(uid = 3L, rank = 3) to + CommunalWidgetItem(uid = 3L, 3, "pk_3/cls_3", 3L), + CommunalItemRank(uid = 4L, rank = 4) to + CommunalWidgetItem(uid = 4L, 4, "pk_4/cls_4", 4L), + ) + fakeProviders.value = + mapOf( + 1 to providerInfoA, + 2 to providerInfoB, + ) + + // Expect to see only widget 1 and 2 + val communalWidgets by collectLastValue(underTest.communalWidgets) + assertThat(communalWidgets) + .containsExactly( + CommunalWidgetContentModel.Available( + appWidgetId = 1, + providerInfo = providerInfoA, + priority = 1, + ), + CommunalWidgetContentModel.Available( + appWidgetId = 2, + providerInfo = providerInfoB, + priority = 2, + ), + ) + } + + @Test + fun communalWidgets_updatedWhenProvidersUpdate() = + testScope.runTest { + // Set up widgets and providers + fakeWidgets.value = + mapOf( + CommunalItemRank(uid = 1L, rank = 1) to + CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L), + CommunalItemRank(uid = 2L, rank = 2) to + CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L), + ) + fakeProviders.value = + mapOf( + 1 to providerInfoA, + 2 to providerInfoB, + ) + + // Expect two widgets + val communalWidgets by collectLastValue(underTest.communalWidgets) + assertThat(communalWidgets).isNotNull() + assertThat(communalWidgets) + .containsExactly( + CommunalWidgetContentModel.Available( + appWidgetId = 1, + providerInfo = providerInfoA, + priority = 1, + ), + CommunalWidgetContentModel.Available( + appWidgetId = 2, + providerInfo = providerInfoB, + priority = 2, + ), + ) + + // Provider info updated for widget 1 + fakeProviders.value = + mapOf( + 1 to providerInfoC, + 2 to providerInfoB, + ) + runCurrent() + + assertThat(communalWidgets) + .containsExactly( + CommunalWidgetContentModel.Available( + appWidgetId = 1, + // Verify that provider info updated + providerInfo = providerInfoC, + priority = 1, + ), + CommunalWidgetContentModel.Available( + appWidgetId = 2, + providerInfo = providerInfoB, + priority = 2, + ), + ) + } + + @Test fun addWidget_allocateId_bindWidget_andAddToDb() = testScope.runTest { val provider = ComponentName("pkg_name", "cls_name") @@ -434,9 +532,102 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() { assertThat(restoredWidget2.rank).isEqualTo(expectedWidget2.rank) } - private fun installedProviders(providers: List<AppWidgetProviderInfo>) { - whenever(appWidgetManager.installedProviders).thenReturn(providers) - } + @Test + fun pendingWidgets() = + testScope.runTest { + fakeWidgets.value = + mapOf( + CommunalItemRank(uid = 1L, rank = 1) to + CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L), + CommunalItemRank(uid = 2L, rank = 2) to + CommunalWidgetItem(uid = 2L, 2, "pk_2/cls_2", 2L), + ) + + // Widget 1 is installed + fakeProviders.value = mapOf(1 to providerInfoA) + + // Widget 2 is pending install + val fakeIcon = mock<Bitmap>() + packageChangeRepository.setInstallSessions( + listOf( + PackageInstallSession( + sessionId = 1, + packageName = "pk_2", + icon = fakeIcon, + user = UserHandle.CURRENT, + ) + ) + ) + + val communalWidgets by collectLastValue(underTest.communalWidgets) + assertThat(communalWidgets) + .containsExactly( + CommunalWidgetContentModel.Available( + appWidgetId = 1, + providerInfo = providerInfoA, + priority = 1, + ), + CommunalWidgetContentModel.Pending( + appWidgetId = 2, + priority = 2, + packageName = "pk_2", + icon = fakeIcon, + user = UserHandle.CURRENT, + ), + ) + } + + @Test + fun pendingWidgets_pendingWidgetBecomesAvailableAfterInstall() = + testScope.runTest { + fakeWidgets.value = + mapOf( + CommunalItemRank(uid = 1L, rank = 1) to + CommunalWidgetItem(uid = 1L, 1, "pk_1/cls_1", 1L), + ) + + // Widget 1 is pending install + val fakeIcon = mock<Bitmap>() + packageChangeRepository.setInstallSessions( + listOf( + PackageInstallSession( + sessionId = 1, + packageName = "pk_1", + icon = fakeIcon, + user = UserHandle.CURRENT, + ) + ) + ) + + val communalWidgets by collectLastValue(underTest.communalWidgets) + assertThat(communalWidgets) + .containsExactly( + CommunalWidgetContentModel.Pending( + appWidgetId = 1, + priority = 1, + packageName = "pk_1", + icon = fakeIcon, + user = UserHandle.CURRENT, + ), + ) + + // Package for widget 1 finished installing + packageChangeRepository.setInstallSessions(emptyList()) + + // Provider info for widget 1 becomes available + fakeProviders.value = mapOf(1 to providerInfoA) + + runCurrent() + + assertThat(communalWidgets) + .containsExactly( + CommunalWidgetContentModel.Available( + appWidgetId = 1, + providerInfo = providerInfoA, + priority = 1, + ), + ) + } private fun setAppWidgetIds(ids: List<Int>) { whenever(appWidgetHost.appWidgetIds).thenReturn(ids.toIntArray()) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index 456fb79d0536..766798c2c2c3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -23,6 +23,7 @@ import android.app.smartspace.SmartspaceTarget import android.appwidget.AppWidgetProviderInfo import android.content.Intent import android.content.pm.UserInfo +import android.graphics.Bitmap import android.os.UserHandle import android.os.UserManager import android.os.userManager @@ -871,7 +872,14 @@ class CommunalInteractorTest : SysuiTestCase() { // One widget is filtered out and the remaining two link to main user id. assertThat(checkNotNull(widgetContent).size).isEqualTo(2) widgetContent!!.forEachIndexed { _, model -> - assertThat(model.providerInfo.profile?.identifier).isEqualTo(MAIN_USER_INFO.id) + assertThat(model is CommunalContentModel.WidgetContent.Widget).isTrue() + assertThat( + (model as CommunalContentModel.WidgetContent.Widget) + .providerInfo + .profile + ?.identifier + ) + .isEqualTo(MAIN_USER_INFO.id) } } @@ -1037,9 +1045,9 @@ class CommunalInteractorTest : SysuiTestCase() { runCurrent() val widgetContent by collectLastValue(underTest.widgetContent) - // Given three widgets, and one of them is associated with work profile. + // One available work widget, one pending work widget, and one regular available widget. val widget1 = createWidgetForUser(1, USER_INFO_WORK.id) - val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id) + val widget2 = createPendingWidgetForUser(2, userId = USER_INFO_WORK.id) val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id) val widgets = listOf(widget1, widget2, widget3) widgetRepository.setCommunalWidgets(widgets) @@ -1049,11 +1057,9 @@ class CommunalInteractorTest : SysuiTestCase() { DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL ) - // Widget under work profile is filtered out and the remaining two link to main user id. - assertThat(widgetContent).hasSize(2) - widgetContent!!.forEach { model -> - assertThat(model.providerInfo.profile?.identifier).isEqualTo(MAIN_USER_INFO.id) - } + // Widgets under work profile are filtered out. Only the regular widget remains. + assertThat(widgetContent).hasSize(1) + assertThat(widgetContent?.get(0)?.appWidgetId).isEqualTo(3) } @Test @@ -1076,7 +1082,7 @@ class CommunalInteractorTest : SysuiTestCase() { val widgetContent by collectLastValue(underTest.widgetContent) // Given three widgets, and one of them is associated with work profile. val widget1 = createWidgetForUser(1, USER_INFO_WORK.id) - val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id) + val widget2 = createPendingWidgetForUser(2, userId = USER_INFO_WORK.id) val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id) val widgets = listOf(widget1, widget2, widget3) widgetRepository.setCommunalWidgets(widgets) @@ -1086,10 +1092,11 @@ class CommunalInteractorTest : SysuiTestCase() { DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE ) - // Widget under work profile is available. + // Widgets under work profile are available. assertThat(widgetContent).hasSize(3) - assertThat(widgetContent!![0].providerInfo.profile?.identifier) - .isEqualTo(USER_INFO_WORK.id) + assertThat(widgetContent?.get(0)?.appWidgetId).isEqualTo(1) + assertThat(widgetContent?.get(1)?.appWidgetId).isEqualTo(2) + assertThat(widgetContent?.get(2)?.appWidgetId).isEqualTo(3) } @Test @@ -1182,8 +1189,11 @@ class CommunalInteractorTest : SysuiTestCase() { ) } - private fun createWidgetForUser(appWidgetId: Int, userId: Int): CommunalWidgetContentModel = - mock<CommunalWidgetContentModel> { + private fun createWidgetForUser( + appWidgetId: Int, + userId: Int + ): CommunalWidgetContentModel.Available = + mock<CommunalWidgetContentModel.Available> { whenever(this.appWidgetId).thenReturn(appWidgetId) val providerInfo = mock<AppWidgetProviderInfo>().apply { @@ -1193,11 +1203,27 @@ class CommunalInteractorTest : SysuiTestCase() { whenever(this.providerInfo).thenReturn(providerInfo) } + private fun createPendingWidgetForUser( + appWidgetId: Int, + priority: Int = 0, + packageName: String = "", + icon: Bitmap? = null, + userId: Int = 0, + ): CommunalWidgetContentModel.Pending { + return CommunalWidgetContentModel.Pending( + appWidgetId = appWidgetId, + priority = priority, + packageName = packageName, + icon = icon, + user = UserHandle(userId), + ) + } + private fun createWidgetWithCategory( appWidgetId: Int, category: Int ): CommunalWidgetContentModel = - mock<CommunalWidgetContentModel> { + mock<CommunalWidgetContentModel.Available> { whenever(this.appWidgetId).thenReturn(appWidgetId) val providerInfo = mock<AppWidgetProviderInfo>().apply { widgetCategory = category } whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt index 02d927a0e5ab..f9d5073799db 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelTest.kt @@ -86,4 +86,26 @@ class CommunalTransitionViewModelTest : SysuiTestCase() { ) assertThat(isUmoOnCommunal).isFalse() } + + @Test + fun testIsUmoOnCommunalDuringTransitionBetweenOccludedAndGlanceableHub() = + testScope.runTest { + val isUmoOnCommunal by collectLastValue(underTest.isUmoOnCommunal) + assertThat(isUmoOnCommunal).isNull() + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.OCCLUDED, + to = KeyguardState.GLANCEABLE_HUB, + testScope + ) + assertThat(isUmoOnCommunal).isTrue() + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.OCCLUDED, + testScope + ) + + assertThat(isUmoOnCommunal).isFalse() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt index 89a4c04015b6..b3a12a69d1af 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt @@ -28,6 +28,8 @@ import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testScope import com.android.systemui.log.logcatLogBuffer import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -36,6 +38,9 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.never +import org.mockito.Mockito.verify @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -96,4 +101,137 @@ class CommunalAppWidgetHostTest : SysuiTestCase() { assertThat(appWidgetIdToRemove).isEqualTo(2) } + + @Test + fun observer_onHostStartListeningTriggeredWhileObserverActive() = + testScope.runTest { + // Observer added + val observer = mock<CommunalAppWidgetHost.Observer>() + underTest.addObserver(observer) + runCurrent() + + // Verify callback triggered + verify(observer, never()).onHostStartListening() + underTest.startListening() + runCurrent() + verify(observer).onHostStartListening() + + clearInvocations(observer) + + // Observer removed + underTest.removeObserver(observer) + runCurrent() + + // Verify callback not triggered + underTest.startListening() + runCurrent() + verify(observer, never()).onHostStartListening() + } + + @Test + fun observer_onHostStopListeningTriggeredWhileObserverActive() = + testScope.runTest { + // Observer added + val observer = mock<CommunalAppWidgetHost.Observer>() + underTest.addObserver(observer) + runCurrent() + + // Verify callback triggered + verify(observer, never()).onHostStopListening() + underTest.stopListening() + runCurrent() + verify(observer).onHostStopListening() + + clearInvocations(observer) + + // Observer removed + underTest.removeObserver(observer) + runCurrent() + + // Verify callback not triggered + underTest.stopListening() + runCurrent() + verify(observer, never()).onHostStopListening() + } + + @Test + fun observer_onAllocateAppWidgetIdTriggeredWhileObserverActive() = + testScope.runTest { + // Observer added + val observer = mock<CommunalAppWidgetHost.Observer>() + underTest.addObserver(observer) + runCurrent() + + // Verify callback triggered + verify(observer, never()).onAllocateAppWidgetId(any()) + val id = underTest.allocateAppWidgetId() + runCurrent() + verify(observer).onAllocateAppWidgetId(eq(id)) + + clearInvocations(observer) + + // Observer removed + underTest.removeObserver(observer) + runCurrent() + + // Verify callback not triggered + underTest.allocateAppWidgetId() + runCurrent() + verify(observer, never()).onAllocateAppWidgetId(any()) + } + + @Test + fun observer_onDeleteAppWidgetIdTriggeredWhileObserverActive() = + testScope.runTest { + // Observer added + val observer = mock<CommunalAppWidgetHost.Observer>() + underTest.addObserver(observer) + runCurrent() + + // Verify callback triggered + verify(observer, never()).onDeleteAppWidgetId(any()) + underTest.deleteAppWidgetId(1) + runCurrent() + verify(observer).onDeleteAppWidgetId(eq(1)) + + clearInvocations(observer) + + // Observer removed + underTest.removeObserver(observer) + runCurrent() + + // Verify callback not triggered + underTest.deleteAppWidgetId(2) + runCurrent() + verify(observer, never()).onDeleteAppWidgetId(any()) + } + + @Test + fun observer_multipleObservers() = + testScope.runTest { + // Set up two observers + val observer1 = mock<CommunalAppWidgetHost.Observer>() + val observer2 = mock<CommunalAppWidgetHost.Observer>() + underTest.addObserver(observer1) + underTest.addObserver(observer2) + runCurrent() + + // Verify both observers triggered + verify(observer1, never()).onHostStartListening() + verify(observer2, never()).onHostStartListening() + underTest.startListening() + runCurrent() + verify(observer1).onHostStartListening() + verify(observer2).onHostStartListening() + + // Observer 1 removed + underTest.removeObserver(observer1) + runCurrent() + + // Verify only observer 2 is triggered + underTest.stopListening() + runCurrent() + verify(observer2).onHostStopListening() + verify(observer1, never()).onHostStopListening() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt index 9aebc305fb56..6ca04dfca6a4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt @@ -123,12 +123,12 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { // Widgets available. val widgets = listOf( - CommunalWidgetContentModel( + CommunalWidgetContentModel.Available( appWidgetId = 0, priority = 30, providerInfo = providerInfo, ), - CommunalWidgetContentModel( + CommunalWidgetContentModel.Available( appWidgetId = 1, priority = 20, providerInfo = providerInfo, @@ -177,12 +177,12 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { // Widgets available. val widgets = listOf( - CommunalWidgetContentModel( + CommunalWidgetContentModel.Available( appWidgetId = 0, priority = 30, providerInfo = providerInfo, ), - CommunalWidgetContentModel( + CommunalWidgetContentModel.Available( appWidgetId = 1, priority = 20, providerInfo = providerInfo, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt index 569116c6124a..2d079d7488d0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt @@ -44,7 +44,6 @@ import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS import com.android.systemui.communal.ui.viewmodel.PopupType import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED import com.android.systemui.flags.andSceneContainer import com.android.systemui.flags.fakeFeatureFlagsClassic @@ -90,7 +89,7 @@ import platform.test.runner.parameterized.Parameters @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { +class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { @Mock private lateinit var mediaHost: MediaHost @Mock private lateinit var user: UserInfo @Mock private lateinit var providerInfo: AppWidgetProviderInfo @@ -111,7 +110,7 @@ class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { private lateinit var underTest: CommunalViewModel init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before @@ -145,7 +144,6 @@ class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { kosmos.communalInteractor, kosmos.communalTutorialInteractor, kosmos.shadeInteractor, - kosmos.deviceEntryInteractor, mediaHost, logcatLogBuffer("CommunalViewModelTest"), ) @@ -186,12 +184,12 @@ class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { // Widgets available. val widgets = listOf( - CommunalWidgetContentModel( + CommunalWidgetContentModel.Available( appWidgetId = 0, priority = 30, providerInfo = providerInfo, ), - CommunalWidgetContentModel( + CommunalWidgetContentModel.Available( appWidgetId = 1, priority = 20, providerInfo = providerInfo, @@ -245,7 +243,7 @@ class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { widgetRepository.setCommunalWidgets( listOf( - CommunalWidgetContentModel( + CommunalWidgetContentModel.Available( appWidgetId = 1, priority = 1, providerInfo = providerInfo, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt index 6cae5d352fc2..3d2eabf2a07c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.communal.widgets import android.appwidget.AppWidgetProviderInfo import android.content.pm.UserInfo +import android.graphics.Bitmap import android.os.UserHandle import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -60,6 +61,7 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() { private val kosmos = testKosmos() @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost + @Mock private lateinit var communalWidgetHost: CommunalWidgetHost private lateinit var appWidgetIdToRemove: MutableSharedFlow<Int> @@ -78,6 +80,7 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() { underTest = CommunalAppWidgetHostStartable( appWidgetHost, + communalWidgetHost, kosmos.communalInteractor, kosmos.fakeUserTracker, kosmos.applicationCoroutineScope, @@ -143,16 +146,44 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() { } @Test + fun observeHostWhenCommunalIsAvailable() = + with(kosmos) { + testScope.runTest { + setCommunalAvailable(true) + communalInteractor.setEditModeOpen(false) + verify(communalWidgetHost, never()).startObservingHost() + verify(communalWidgetHost, never()).stopObservingHost() + + underTest.start() + runCurrent() + + verify(communalWidgetHost).startObservingHost() + verify(communalWidgetHost, never()).stopObservingHost() + + setCommunalAvailable(false) + runCurrent() + + verify(communalWidgetHost).stopObservingHost() + } + } + + @Test fun removeAppWidgetReportedByHost() = with(kosmos) { testScope.runTest { // Set up communal widgets val widget1 = - mock<CommunalWidgetContentModel> { whenever(this.appWidgetId).thenReturn(1) } + mock<CommunalWidgetContentModel.Available> { + whenever(this.appWidgetId).thenReturn(1) + } val widget2 = - mock<CommunalWidgetContentModel> { whenever(this.appWidgetId).thenReturn(2) } + mock<CommunalWidgetContentModel.Available> { + whenever(this.appWidgetId).thenReturn(2) + } val widget3 = - mock<CommunalWidgetContentModel> { whenever(this.appWidgetId).thenReturn(3) } + mock<CommunalWidgetContentModel.Available> { + whenever(this.appWidgetId).thenReturn(3) + } fakeCommunalWidgetRepository.setCommunalWidgets(listOf(widget1, widget2, widget3)) underTest.start() @@ -184,8 +215,9 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() { userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK), selectedUserIndex = 0, ) + // One work widget, one pending work widget, and one personal widget. val widget1 = createWidgetForUser(1, USER_INFO_WORK.id) - val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id) + val widget2 = createPendingWidgetForUser(2, USER_INFO_WORK.id) val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id) val widgets = listOf(widget1, widget2, widget3) fakeCommunalWidgetRepository.setCommunalWidgets(widgets) @@ -209,8 +241,8 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() { fakeKeyguardRepository.setKeyguardShowing(true) runCurrent() - // Widget created for work profile is removed. - assertThat(communalWidgets).containsExactly(widget2, widget3) + // Both work widgets are removed. + assertThat(communalWidgets).containsExactly(widget3) } } @@ -227,14 +259,32 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() { ) } - private fun createWidgetForUser(appWidgetId: Int, userId: Int): CommunalWidgetContentModel = - mock<CommunalWidgetContentModel> { + private fun createWidgetForUser( + appWidgetId: Int, + userId: Int + ): CommunalWidgetContentModel.Available = + mock<CommunalWidgetContentModel.Available> { whenever(this.appWidgetId).thenReturn(appWidgetId) val providerInfo = mock<AppWidgetProviderInfo>() whenever(providerInfo.profile).thenReturn(UserHandle(userId)) whenever(this.providerInfo).thenReturn(providerInfo) } + private fun createPendingWidgetForUser( + appWidgetId: Int, + userId: Int, + priority: Int = 0, + packageName: String = "", + icon: Bitmap? = null, + ): CommunalWidgetContentModel.Pending = + CommunalWidgetContentModel.Pending( + appWidgetId = appWidgetId, + priority = priority, + packageName = packageName, + icon = icon, + user = UserHandle(userId), + ) + private companion object { val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN) val USER_INFO_WORK = UserInfo(10, "work", UserInfo.FLAG_PROFILE) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt index 88f5e1b85840..054e516db943 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.communal.widgets +import android.appwidget.AppWidgetHost import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProviderInfo import android.content.ComponentName @@ -26,6 +27,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testScope import com.android.systemui.log.logcatLogBuffer import com.android.systemui.testKosmos @@ -40,6 +43,7 @@ import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat import java.util.Optional import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -47,6 +51,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.eq import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -59,6 +65,10 @@ class CommunalWidgetHostTest : SysuiTestCase() { @Mock private lateinit var appWidgetManager: AppWidgetManager @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost + @Mock private lateinit var providerInfo1: AppWidgetProviderInfo + @Mock private lateinit var providerInfo2: AppWidgetProviderInfo + @Mock private lateinit var providerInfo3: AppWidgetProviderInfo + private val selectedUserInteractor: SelectedUserInteractor by lazy { kosmos.selectedUserInteractor } @@ -69,8 +79,19 @@ class CommunalWidgetHostTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) + whenever( + appWidgetManager.bindAppWidgetIdIfAllowed( + any<Int>(), + any<UserHandle>(), + any<ComponentName>(), + any<Bundle>() + ) + ) + .thenReturn(true) + underTest = CommunalWidgetHost( + kosmos.applicationCoroutineScope, Optional.of(appWidgetManager), appWidgetHost, selectedUserInteractor, @@ -89,15 +110,6 @@ class CommunalWidgetHostTest : SysuiTestCase() { val user = UserHandle(checkNotNull(userId)) whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(widgetId) - whenever( - appWidgetManager.bindAppWidgetIdIfAllowed( - any<Int>(), - any<UserHandle>(), - any<ComponentName>(), - any<Bundle>(), - ) - ) - .thenReturn(true) // bind the widget with the current user when no user is explicitly set val result = underTest.allocateIdAndBindWidget(provider) @@ -121,15 +133,6 @@ class CommunalWidgetHostTest : SysuiTestCase() { val user = UserHandle(0) whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(widgetId) - whenever( - appWidgetManager.bindAppWidgetIdIfAllowed( - any<Int>(), - any<UserHandle>(), - any<ComponentName>(), - any<Bundle>() - ) - ) - .thenReturn(true) // provider and user handle are both set val result = underTest.allocateIdAndBindWidget(provider, user) @@ -172,6 +175,261 @@ class CommunalWidgetHostTest : SysuiTestCase() { assertThat(result).isNull() } + @Test + fun listener_exactlyOneListenerRegisteredForEachWidgetWhenHostStartListening() = + testScope.runTest { + // 3 widgets registered with the host + whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2, 3)) + + underTest.startObservingHost() + runCurrent() + + // Make sure no listener is set before host starts listening + verify(appWidgetHost, never()).setListener(any(), any()) + + // Host starts listening + val observer = + withArgCaptor<CommunalAppWidgetHost.Observer> { + verify(appWidgetHost).addObserver(capture()) + } + observer.onHostStartListening() + runCurrent() + + // Verify a listener is set for each widget + verify(appWidgetHost, times(3)).setListener(any(), any()) + verify(appWidgetHost).setListener(eq(1), any()) + verify(appWidgetHost).setListener(eq(2), any()) + verify(appWidgetHost).setListener(eq(3), any()) + } + + @Test + fun listener_listenersRemovedWhenHostStopListening() = + testScope.runTest { + // 3 widgets registered with the host + whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2, 3)) + + underTest.startObservingHost() + runCurrent() + + // Host starts listening + val observer = + withArgCaptor<CommunalAppWidgetHost.Observer> { + verify(appWidgetHost).addObserver(capture()) + } + observer.onHostStartListening() + runCurrent() + + // Verify none of the listener is removed before host stop listening + verify(appWidgetHost, never()).removeListener(any()) + + observer.onHostStopListening() + + // Verify each listener is removed + verify(appWidgetHost, times(3)).removeListener(any()) + verify(appWidgetHost).removeListener(eq(1)) + verify(appWidgetHost).removeListener(eq(2)) + verify(appWidgetHost).removeListener(eq(3)) + } + + @Test + fun listener_addNewListenerWhenNewIdAllocated() = + testScope.runTest { + whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf()) + val observer = start() + + // Verify no listener is set before a new app widget id is allocated + verify(appWidgetHost, never()).setListener(any(), any()) + + // Allocate an app widget id + observer.onAllocateAppWidgetId(1) + + // Verify new listener set for that app widget id + verify(appWidgetHost).setListener(eq(1), any()) + } + + @Test + fun listener_removeListenerWhenWidgetDeleted() = + testScope.runTest { + whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1)) + val observer = start() + + // Verify listener not removed before widget deleted + verify(appWidgetHost, never()).removeListener(eq(1)) + + // Widget deleted + observer.onDeleteAppWidgetId(1) + + // Verify listener removed for that widget + verify(appWidgetHost).removeListener(eq(1)) + } + + @Test + fun providerInfo_populatesWhenStartListening() = + testScope.runTest { + whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2)) + whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfo1) + whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfo2) + + val providerInfoValues by collectValues(underTest.appWidgetProviders) + + // Assert that the map is empty before host starts listening + assertThat(providerInfoValues).hasSize(1) + assertThat(providerInfoValues[0]).isEmpty() + + start() + runCurrent() + + // Assert that the provider info map is populated after host started listening, and that + // all providers are emitted at once + assertThat(providerInfoValues).hasSize(2) + assertThat(providerInfoValues[1]) + .containsExactlyEntriesIn( + mapOf( + Pair(1, providerInfo1), + Pair(2, providerInfo2), + ) + ) + } + + @Test + fun providerInfo_clearsWhenStopListening() = + testScope.runTest { + whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2)) + whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfo1) + whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfo2) + + val observer = start() + runCurrent() + + // Assert that the provider info map is populated + val providerInfo by collectLastValue(underTest.appWidgetProviders) + assertThat(providerInfo) + .containsExactlyEntriesIn( + mapOf( + Pair(1, providerInfo1), + Pair(2, providerInfo2), + ) + ) + + // Host stop listening + observer.onHostStopListening() + + // Assert that the provider info map is cleared + assertThat(providerInfo).isEmpty() + } + + @Test + fun providerInfo_onUpdate() = + testScope.runTest { + whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2)) + whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfo1) + whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfo2) + + val providerInfo by collectLastValue(underTest.appWidgetProviders) + + start() + runCurrent() + + // Assert that the provider info map is populated + assertThat(providerInfo) + .containsExactlyEntriesIn( + mapOf( + Pair(1, providerInfo1), + Pair(2, providerInfo2), + ) + ) + + // Provider info for widget 1 updated + val listener = + withArgCaptor<AppWidgetHost.AppWidgetHostListener> { + verify(appWidgetHost).setListener(eq(1), capture()) + } + listener.onUpdateProviderInfo(providerInfo3) + runCurrent() + + // Assert that the update is reflected in the flow + assertThat(providerInfo) + .containsExactlyEntriesIn( + mapOf( + Pair(1, providerInfo3), + Pair(2, providerInfo2), + ) + ) + } + + @Test + fun providerInfo_updateWhenANewWidgetIsBound() = + testScope.runTest { + whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2)) + whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfo1) + whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfo2) + + val providerInfo by collectLastValue(underTest.appWidgetProviders) + + start() + runCurrent() + + // Assert that the provider info map is populated + assertThat(providerInfo) + .containsExactlyEntriesIn( + mapOf( + Pair(1, providerInfo1), + Pair(2, providerInfo2), + ) + ) + + // Bind a new widget + whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(3) + whenever(appWidgetManager.getAppWidgetInfo(3)).thenReturn(providerInfo3) + val newWidgetComponentName = ComponentName.unflattenFromString("pkg_new/cls_new")!! + underTest.allocateIdAndBindWidget(newWidgetComponentName) + runCurrent() + + // Assert that the new provider is reflected in the flow + assertThat(providerInfo) + .containsExactlyEntriesIn( + mapOf( + Pair(1, providerInfo1), + Pair(2, providerInfo2), + Pair(3, providerInfo3), + ) + ) + } + + @Test + fun providerInfo_updateWhenWidgetRemoved() = + testScope.runTest { + whenever(appWidgetHost.appWidgetIds).thenReturn(intArrayOf(1, 2)) + whenever(appWidgetManager.getAppWidgetInfo(1)).thenReturn(providerInfo1) + whenever(appWidgetManager.getAppWidgetInfo(2)).thenReturn(providerInfo2) + + val providerInfo by collectLastValue(underTest.appWidgetProviders) + + val observer = start() + runCurrent() + + // Assert that the provider info map is populated + assertThat(providerInfo) + .containsExactlyEntriesIn( + mapOf( + Pair(1, providerInfo1), + Pair(2, providerInfo2), + ) + ) + + // Remove widget 1 + observer.onDeleteAppWidgetId(1) + runCurrent() + + // Assert that provider info for widget 1 is removed + assertThat(providerInfo) + .containsExactlyEntriesIn( + mapOf( + Pair(2, providerInfo2), + ) + ) + } + private fun selectUser() { kosmos.fakeUserRepository.selectedUser.value = SelectedUserModel( @@ -179,4 +437,16 @@ class CommunalWidgetHostTest : SysuiTestCase() { selectionStatus = SelectionStatus.SELECTION_COMPLETE ) } + + private fun TestScope.start(): CommunalAppWidgetHost.Observer { + underTest.startObservingHost() + runCurrent() + + val observer = + withArgCaptor<CommunalAppWidgetHost.Observer> { + verify(appWidgetHost).addObserver(capture()) + } + observer.onHostStartListening() + return observer + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java index 2b3f40f7323f..e3dd9aefa6d0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java @@ -170,7 +170,7 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { @Test public void testBurnInProtectionStopsWhenContentViewDetached() { mController.onViewDetached(); - verify(mHandler).removeCallbacks(any(Runnable.class)); + verify(mHandler).removeCallbacksAndMessages(null); } @Test @@ -281,4 +281,16 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { verify(mAnimationsController).cancelAnimations(); } + + @Test + public void onViewAttached_addsScrimExpansionCallback() { + mController.onViewAttached(); + verify(mBouncerlessScrimController).addCallback(any()); + } + + @Test + public void onViewDetached_removesScrimExpansionCallback() { + mController.onViewDetached(); + verify(mBouncerlessScrimController).removeCallback(any()); + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt index feb72989980c..7292985b2dba 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsComponentInteractorTest.kt @@ -33,7 +33,6 @@ import com.android.systemui.controls.panels.SelectedComponentRepository import com.android.systemui.controls.panels.authorizedPanelsRepository import com.android.systemui.controls.panels.selectedComponentRepository import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor.Companion.MAX_UPDATE_CORRELATION_DELAY import com.android.systemui.kosmos.testScope import com.android.systemui.settings.fakeUserTracker @@ -64,7 +63,7 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() - private lateinit var underTest: HomeControlsComponentInteractor + private val underTest by lazy { kosmos.homeControlsComponentInteractor } @Before fun setUp() = @@ -73,8 +72,7 @@ class HomeControlsComponentInteractorTest : SysuiTestCase() { fakeUserRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER)) whenever(controlsComponent.getControlsListingController()) .thenReturn(Optional.of(controlsListingController)) - - underTest = homeControlsComponentInteractor + Unit } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt index 3b6f6a2d5e1a..f31eb7f50405 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt @@ -58,7 +58,7 @@ import platform.test.runner.parameterized.Parameters @ExperimentalCoroutinesApi @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class UdfpsKeyguardInteractorTest(flags: FlagsParameterization?) : SysuiTestCase() { +class UdfpsKeyguardInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { val kosmos = testKosmos() val testScope = kosmos.testScope val keyguardRepository = kosmos.fakeKeyguardRepository @@ -88,7 +88,7 @@ class UdfpsKeyguardInteractorTest(flags: FlagsParameterization?) : SysuiTestCase } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt index e270d9efec97..519bb6eb7834 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt @@ -62,8 +62,8 @@ class AodBurnInViewModelTest : SysuiTestCase() { private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository private val keyguardClockRepository = kosmos.fakeKeyguardClockRepository private lateinit var underTest: AodBurnInViewModel - - private var burnInParameters = BurnInParameters() + // assign a smaller value to minViewY to avoid overflow + private var burnInParameters = BurnInParameters(minViewY = Int.MAX_VALUE / 2) private val burnInFlow = MutableStateFlow(BurnInModel()) @Before @@ -296,52 +296,111 @@ class AodBurnInViewModelTest : SysuiTestCase() { scale = 0.5f, ) - assertThat(movement?.translationX).isEqualTo(0) - assertThat(movement?.translationY).isEqualTo(0) + assertThat(movement?.translationX).isEqualTo(20) + assertThat(movement?.translationY).isEqualTo(30) assertThat(movement?.scale).isEqualTo(0.5f) assertThat(movement?.scaleClockOnly).isEqualTo(false) } @Test + fun translationAndScale_composeFlagOff_weatherLargeClock() = + testBurnInViewModelForClocks( + isSmallClock = false, + isWeatherClock = true, + expectedScaleOnly = false, + enableMigrateClocksToBlueprintFlag = true, + enableComposeLockscreenFlag = false + ) + + @Test + fun translationAndScale_composeFlagOff_weatherSmallClock() = + testBurnInViewModelForClocks( + isSmallClock = true, + isWeatherClock = true, + expectedScaleOnly = false, + enableMigrateClocksToBlueprintFlag = true, + enableComposeLockscreenFlag = false + ) + + @Test + fun translationAndScale_composeFlagOff_nonWeatherLargeClock() = + testBurnInViewModelForClocks( + isSmallClock = false, + isWeatherClock = false, + expectedScaleOnly = true, + enableMigrateClocksToBlueprintFlag = true, + enableComposeLockscreenFlag = false + ) + + @Test + fun translationAndScale_composeFlagOff_nonWeatherSmallClock() = + testBurnInViewModelForClocks( + isSmallClock = true, + isWeatherClock = false, + expectedScaleOnly = false, + enableMigrateClocksToBlueprintFlag = true, + enableComposeLockscreenFlag = false + ) + + @Test fun translationAndScale_composeFlagOn_weatherLargeClock() = - testBurnInViewModelWhenComposeFlagOn( + testBurnInViewModelForClocks( isSmallClock = false, isWeatherClock = true, - expectedScaleOnly = false + expectedScaleOnly = false, + enableMigrateClocksToBlueprintFlag = true, + enableComposeLockscreenFlag = true ) @Test fun translationAndScale_composeFlagOn_weatherSmallClock() = - testBurnInViewModelWhenComposeFlagOn( + testBurnInViewModelForClocks( isSmallClock = true, isWeatherClock = true, - expectedScaleOnly = true + expectedScaleOnly = false, + enableMigrateClocksToBlueprintFlag = true, + enableComposeLockscreenFlag = true ) @Test fun translationAndScale_composeFlagOn_nonWeatherLargeClock() = - testBurnInViewModelWhenComposeFlagOn( + testBurnInViewModelForClocks( isSmallClock = false, isWeatherClock = false, - expectedScaleOnly = true + expectedScaleOnly = true, + enableMigrateClocksToBlueprintFlag = true, + enableComposeLockscreenFlag = true ) @Test fun translationAndScale_composeFlagOn_nonWeatherSmallClock() = - testBurnInViewModelWhenComposeFlagOn( + testBurnInViewModelForClocks( isSmallClock = true, isWeatherClock = false, - expectedScaleOnly = true + expectedScaleOnly = false, + enableMigrateClocksToBlueprintFlag = true, + enableComposeLockscreenFlag = true ) - private fun testBurnInViewModelWhenComposeFlagOn( + private fun testBurnInViewModelForClocks( isSmallClock: Boolean, isWeatherClock: Boolean, - expectedScaleOnly: Boolean + expectedScaleOnly: Boolean, + enableMigrateClocksToBlueprintFlag: Boolean, + enableComposeLockscreenFlag: Boolean ) = testScope.runTest { - mSetFlagsRule.enableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - mSetFlagsRule.enableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) + if (enableMigrateClocksToBlueprintFlag) { + mSetFlagsRule.enableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) + } else { + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) + } + + if (enableComposeLockscreenFlag) { + mSetFlagsRule.enableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) + } else { + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_COMPOSE_LOCKSCREEN) + } if (isSmallClock) { keyguardClockRepository.setClockSize(ClockSize.SMALL) // we need the following step to update stateFlow value diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt index f52c66e24907..cde703b70fe6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt @@ -43,7 +43,7 @@ import platform.test.runner.parameterized.Parameters @ExperimentalCoroutinesApi @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class AodToLockscreenTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { +class AodToLockscreenTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { val kosmos = testKosmos() val testScope = kosmos.testScope val repository = kosmos.fakeKeyguardTransitionRepository @@ -60,7 +60,7 @@ class AodToLockscreenTransitionViewModelTest(flags: FlagsParameterization?) : Sy } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt index fee18dd94a91..d63293675034 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt @@ -16,13 +16,15 @@ package com.android.systemui.keyguard.ui.viewmodel -import androidx.test.ext.junit.runners.AndroidJUnit4 +import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.domain.interactor.mockPrimaryBouncerInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.flags.BrokenWithSceneContainer import com.android.systemui.flags.Flags +import com.android.systemui.flags.andSceneContainer import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState @@ -31,29 +33,25 @@ import com.android.systemui.keyguard.shared.model.ScrimAlpha import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope -import com.android.systemui.shade.data.repository.shadeRepository -import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.shadeTestUtil import com.android.systemui.statusbar.sysuiStatusBarStateController import com.android.systemui.testKosmos import com.android.systemui.util.mockito.whenever import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.milliseconds -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(AndroidJUnit4::class) -class BouncerToGoneFlowsTest : SysuiTestCase() { - @Mock private lateinit var shadeInteractor: ShadeInteractor - - private val shadeExpansionStateFlow = MutableStateFlow(0.1f) +@RunWith(ParameterizedAndroidJunit4::class) +class BouncerToGoneFlowsTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos().apply { @@ -61,16 +59,31 @@ class BouncerToGoneFlowsTest : SysuiTestCase() { } private val testScope = kosmos.testScope private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository - private val shadeRepository = kosmos.shadeRepository private val sysuiStatusBarStateController = kosmos.sysuiStatusBarStateController private val primaryBouncerInteractor = kosmos.mockPrimaryBouncerInteractor - private val underTest = kosmos.bouncerToGoneFlows + + private val shadeTestUtil by lazy { kosmos.shadeTestUtil } + + private lateinit var underTest: BouncerToGoneFlows + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf().andSceneContainer() + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } @Before fun setUp() { MockitoAnnotations.initMocks(this) whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false) sysuiStatusBarStateController.setLeaveOpenOnKeyguardHide(false) + underTest = kosmos.bouncerToGoneFlows } @Test @@ -79,7 +92,7 @@ class BouncerToGoneFlowsTest : SysuiTestCase() { val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER)) runCurrent() - shadeRepository.setLockscreenShadeExpansion(1f) + shadeTestUtil.setLockscreenShadeExpansion(1f) whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true) keyguardTransitionRepository.sendTransitionSteps( @@ -99,12 +112,13 @@ class BouncerToGoneFlowsTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun scrimAlpha_runDimissFromKeyguard_shadeNotExpanded() = testScope.runTest { val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER)) runCurrent() - shadeRepository.setLockscreenShadeExpansion(0f) + shadeTestUtil.setLockscreenShadeExpansion(0f) whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index 2e1765a641b8..838b2a79adff 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -60,7 +60,7 @@ import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class KeyguardRootViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { +class KeyguardRootViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository } @@ -75,7 +75,6 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() private val viewState = ViewStateAccessor() - // add to init block companion object { @JvmStatic @Parameters(name = "{0}") @@ -85,7 +84,7 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt index ec2cb049836f..de4b999b3899 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt @@ -47,7 +47,7 @@ import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class LockscreenContentViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { +class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos: Kosmos = testKosmos() @@ -62,7 +62,7 @@ class LockscreenContentViewModelTest(flags: FlagsParameterization?) : SysuiTestC } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index bc9d257b3836..f46ca002a103 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -24,6 +24,7 @@ import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.Swipe import com.android.compose.animation.scene.SwipeDirection +import com.android.compose.animation.scene.TransitionKey import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository @@ -39,6 +40,7 @@ import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.power.shared.model.WakefulnessState import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.scene.shared.model.TransitionKeys import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shade.shared.model.ShadeMode @@ -117,6 +119,17 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { } } + private fun expectedDownTransitionKey( + isSingleShade: Boolean, + isShadeTouchable: Boolean, + ): TransitionKey? { + return when { + !isShadeTouchable -> null + !isSingleShade -> TransitionKeys.ToSplitShade + else -> null + } + } + private fun expectedUpDestination( canSwipeToEnter: Boolean, isShadeTouchable: Boolean, @@ -184,18 +197,16 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { ) val destinationScenes by collectLastValue(underTest.destinationScenes) - - assertThat( - destinationScenes - ?.get( - Swipe( - SwipeDirection.Down, - fromSource = Edge.Top.takeIf { downFromEdge }, - pointerCount = if (downWithTwoPointers) 2 else 1, - ) - ) - ?.toScene + val downDestination = + destinationScenes?.get( + Swipe( + SwipeDirection.Down, + fromSource = Edge.Top.takeIf { downFromEdge }, + pointerCount = if (downWithTwoPointers) 2 else 1, + ) ) + + assertThat(downDestination?.toScene) .isEqualTo( expectedDownDestination( downFromEdge = downFromEdge, @@ -204,6 +215,14 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { ) ) + assertThat(downDestination?.transitionKey) + .isEqualTo( + expectedDownTransitionKey( + isSingleShade = isSingleShade, + isShadeTouchable = isShadeTouchable, + ) + ) + assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene) .isEqualTo( expectedUpDestination( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt index e3ae3ba4cedd..bc381f2abc6d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt @@ -49,7 +49,7 @@ import platform.test.runner.parameterized.Parameters @ExperimentalCoroutinesApi @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class LockscreenToAodTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { +class LockscreenToAodTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos().apply { fakeFeatureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) } @@ -73,7 +73,7 @@ class LockscreenToAodTransitionViewModelTest(flags: FlagsParameterization?) : Sy } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt index adeb395fc142..933779312df5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt @@ -51,7 +51,7 @@ import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class LockscreenToDreamingTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { +class LockscreenToDreamingTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos().apply { @@ -73,7 +73,7 @@ class LockscreenToDreamingTransitionViewModelTest(flags: FlagsParameterization?) } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt index f8da74fdd742..6ce7e88edc2b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt @@ -54,7 +54,7 @@ import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class LockscreenToOccludedTransitionViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { +class LockscreenToOccludedTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos().apply { fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } @@ -76,7 +76,7 @@ class LockscreenToOccludedTransitionViewModelTest(flags: FlagsParameterization?) } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt index d5df159d6d1e..58c6817c4270 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt @@ -46,7 +46,7 @@ import platform.test.runner.parameterized.Parameters @ExperimentalCoroutinesApi @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameterization?) : +class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos().apply { @@ -67,7 +67,7 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameteriza } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt index 1e5f31401e20..7a37a9e03b16 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt @@ -150,7 +150,7 @@ class MediaFilterRepositoryTest : SysuiTestCase() { @Test fun addMediaControlPlayingThenRemote() = testScope.runTest { - val sortedMedia by collectLastValue(underTest.sortedMedia) + val currentMedia by collectLastValue(underTest.currentMedia) val playingInstanceId = InstanceId.fakeInstanceId(123) val remoteInstanceId = InstanceId.fakeInstanceId(321) val playingData = createMediaData("app1", true, LOCAL, false, playingInstanceId) @@ -161,8 +161,8 @@ class MediaFilterRepositoryTest : SysuiTestCase() { underTest.addSelectedUserMediaEntry(remoteData) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(remoteInstanceId)) - assertThat(sortedMedia?.size).isEqualTo(2) - assertThat(sortedMedia?.values) + assertThat(currentMedia?.size).isEqualTo(2) + assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId)), MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(remoteInstanceId)) @@ -173,7 +173,7 @@ class MediaFilterRepositoryTest : SysuiTestCase() { @Test fun switchMediaControlsPlaying() = testScope.runTest { - val sortedMedia by collectLastValue(underTest.sortedMedia) + val currentMedia by collectLastValue(underTest.currentMedia) val playingInstanceId1 = InstanceId.fakeInstanceId(123) val playingInstanceId2 = InstanceId.fakeInstanceId(321) var playingData1 = createMediaData("app1", true, LOCAL, false, playingInstanceId1) @@ -184,8 +184,8 @@ class MediaFilterRepositoryTest : SysuiTestCase() { underTest.addSelectedUserMediaEntry(playingData2) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId2)) - assertThat(sortedMedia?.size).isEqualTo(2) - assertThat(sortedMedia?.values) + assertThat(currentMedia?.size).isEqualTo(2) + assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId1)), MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId2)) @@ -198,12 +198,28 @@ class MediaFilterRepositoryTest : SysuiTestCase() { underTest.addSelectedUserMediaEntry(playingData1) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId1)) underTest.addSelectedUserMediaEntry(playingData2) - underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId2)) + underTest.addMediaDataLoadingState( + MediaDataLoadingModel.Loaded(playingInstanceId2, false) + ) - assertThat(sortedMedia?.size).isEqualTo(2) - assertThat(sortedMedia?.values) + assertThat(currentMedia?.size).isEqualTo(2) + assertThat(currentMedia) .containsExactly( - MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId2)), + MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId1)), + MediaCommonModel.MediaControl( + MediaDataLoadingModel.Loaded(playingInstanceId2, false) + ) + ) + .inOrder() + + underTest.setOrderedMedia() + + assertThat(currentMedia?.size).isEqualTo(2) + assertThat(currentMedia) + .containsExactly( + MediaCommonModel.MediaControl( + MediaDataLoadingModel.Loaded(playingInstanceId2, false) + ), MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId1)) ) .inOrder() @@ -212,7 +228,7 @@ class MediaFilterRepositoryTest : SysuiTestCase() { @Test fun fullOrderTest() = testScope.runTest { - val sortedMedia by collectLastValue(underTest.sortedMedia) + val currentMedia by collectLastValue(underTest.currentMedia) val instanceId1 = InstanceId.fakeInstanceId(123) val instanceId2 = InstanceId.fakeInstanceId(456) val instanceId3 = InstanceId.fakeInstanceId(321) @@ -252,8 +268,8 @@ class MediaFilterRepositoryTest : SysuiTestCase() { SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true) ) - assertThat(sortedMedia?.size).isEqualTo(6) - assertThat(sortedMedia?.values) + assertThat(currentMedia?.size).isEqualTo(6) + assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(instanceId1)), MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(instanceId2)), @@ -270,7 +286,7 @@ class MediaFilterRepositoryTest : SysuiTestCase() { @Test fun loadMediaFromRec() = testScope.runTest { - val isMediaFromRec by collectLastValue(underTest.isMediaFromRec) + val currentMedia by collectLastValue(underTest.currentMedia) val instanceId1 = InstanceId.fakeInstanceId(123) val instanceId2 = InstanceId.fakeInstanceId(456) val data = @@ -278,22 +294,59 @@ class MediaFilterRepositoryTest : SysuiTestCase() { active = true, instanceId = instanceId1, packageName = PACKAGE_NAME, - isPlaying = true + isPlaying = true, + notificationKey = KEY, + ) + val newData = + MediaData( + active = true, + instanceId = instanceId2, + isPlaying = true, + notificationKey = KEY_2 + ) + val icon = Icon.createWithResource(context, R.drawable.ic_media_play) + val mediaRecommendations = + SmartspaceMediaData( + targetId = KEY_MEDIA_SMARTSPACE, + isActive = true, + packageName = PACKAGE_NAME, + recommendations = MediaTestHelper.getValidRecommendationList(icon), ) - val newData = MediaData(active = true, instanceId = instanceId2) - - assertThat(isMediaFromRec).isFalse() underTest.setMediaFromRecPackageName(PACKAGE_NAME) underTest.addSelectedUserMediaEntry(data) + underTest.setRecommendation(mediaRecommendations) + underTest.setRecommendationsLoadingState( + SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE) + ) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId1)) - assertThat(isMediaFromRec).isTrue() + assertThat(currentMedia) + .containsExactly( + MediaCommonModel.MediaControl( + MediaDataLoadingModel.Loaded(instanceId1), + isMediaFromRec = true + ), + MediaCommonModel.MediaRecommendations( + SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE) + ) + ) + .inOrder() underTest.addSelectedUserMediaEntry(newData) + underTest.addSelectedUserMediaEntry(data.copy(isPlaying = false)) underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId2)) + underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId1)) - assertThat(isMediaFromRec).isFalse() + assertThat(currentMedia) + .containsExactly( + MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(instanceId2)), + MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(instanceId1)), + MediaCommonModel.MediaRecommendations( + SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE) + ) + ) + .inOrder() } private fun createMediaData( @@ -316,6 +369,7 @@ class MediaFilterRepositoryTest : SysuiTestCase() { private const val LOCAL = MediaData.PLAYBACK_LOCAL private const val REMOTE = MediaData.PLAYBACK_CAST_LOCAL private const val KEY = "KEY" + private const val KEY_2 = "KEY_2" private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID" private const val PACKAGE_NAME = "com.android.example" } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt index e44affc7262b..39dbc7e8cb5b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt @@ -55,6 +55,14 @@ class MediaCarouselInteractorTest : SysuiTestCase() { private val mediaFilterRepository: MediaFilterRepository = kosmos.mediaFilterRepository private val mediaRecommendationsInteractor: MediaRecommendationsInteractor = kosmos.mediaRecommendationsInteractor + val icon = Icon.createWithResource(context, R.drawable.ic_media_play) + private val mediaRecommendation = + SmartspaceMediaData( + targetId = KEY_MEDIA_SMARTSPACE, + isActive = true, + packageName = PACKAGE_NAME, + recommendations = MediaTestHelper.getValidRecommendationList(icon), + ) private val underTest: MediaCarouselInteractor = kosmos.mediaCarouselInteractor @@ -122,26 +130,19 @@ class MediaCarouselInteractorTest : SysuiTestCase() { collectLastValue(underTest.hasActiveMediaOrRecommendation) val hasAnyMediaOrRecommendation by collectLastValue(underTest.hasAnyMediaOrRecommendation) - val sortedMedia by collectLastValue(underTest.sortedMedia) + val currentMedia by collectLastValue(underTest.currentMedia) kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) - val icon = Icon.createWithResource(context, R.drawable.ic_media_play) - val userMediaRecommendation = - SmartspaceMediaData( - targetId = KEY_MEDIA_SMARTSPACE, - isActive = true, - recommendations = MediaTestHelper.getValidRecommendationList(icon), - ) val userMedia = MediaData(active = false) val recsLoadingModel = SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE, true) val mediaLoadingModel = MediaDataLoadingModel.Loaded(userMedia.instanceId) - mediaFilterRepository.setRecommendation(userMediaRecommendation) + mediaFilterRepository.setRecommendation(mediaRecommendation) mediaFilterRepository.setRecommendationsLoadingState(recsLoadingModel) assertThat(hasActiveMediaOrRecommendation).isTrue() assertThat(hasAnyMediaOrRecommendation).isTrue() - assertThat(sortedMedia) + assertThat(currentMedia) .containsExactly(MediaCommonModel.MediaRecommendations(recsLoadingModel)) mediaFilterRepository.addSelectedUserMediaEntry(userMedia) @@ -149,7 +150,7 @@ class MediaCarouselInteractorTest : SysuiTestCase() { assertThat(hasActiveMediaOrRecommendation).isTrue() assertThat(hasAnyMediaOrRecommendation).isTrue() - assertThat(sortedMedia) + assertThat(currentMedia) .containsExactly( MediaCommonModel.MediaRecommendations(recsLoadingModel), MediaCommonModel.MediaControl(mediaLoadingModel, true) @@ -166,14 +167,6 @@ class MediaCarouselInteractorTest : SysuiTestCase() { collectLastValue(underTest.hasAnyMediaOrRecommendation) kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) - val icon = Icon.createWithResource(context, R.drawable.ic_media_play) - val mediaRecommendation = - SmartspaceMediaData( - targetId = KEY_MEDIA_SMARTSPACE, - isActive = true, - recommendations = MediaTestHelper.getValidRecommendationList(icon), - ) - mediaFilterRepository.setRecommendation(mediaRecommendation) assertThat(hasActiveMediaOrRecommendation).isTrue() @@ -194,14 +187,6 @@ class MediaCarouselInteractorTest : SysuiTestCase() { collectLastValue(underTest.hasAnyMediaOrRecommendation) kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) - val icon = Icon.createWithResource(context, R.drawable.ic_media_play) - val mediaRecommendation = - SmartspaceMediaData( - targetId = KEY_MEDIA_SMARTSPACE, - isActive = true, - recommendations = MediaTestHelper.getValidRecommendationList(icon), - ) - mediaFilterRepository.setRecommendation(mediaRecommendation) assertThat(hasActiveMediaOrRecommendation).isTrue() @@ -234,26 +219,42 @@ class MediaCarouselInteractorTest : SysuiTestCase() { @Test fun loadMediaFromRec() = testScope.runTest { - val isMediaFromRec by collectLastValue(underTest.isMediaFromRec) + val currentMedia by collectLastValue(underTest.currentMedia) val instanceId = InstanceId.fakeInstanceId(123) - val data = MediaData(active = true, instanceId = instanceId, packageName = PACKAGE_NAME) - - assertThat(isMediaFromRec).isFalse() + val data = + MediaData( + active = true, + instanceId = instanceId, + packageName = PACKAGE_NAME, + notificationKey = KEY + ) + val smartspaceLoadingModel = SmartspaceMediaLoadingModel.Loaded(KEY_MEDIA_SMARTSPACE) + val mediaLoadingModel = MediaDataLoadingModel.Loaded(instanceId) + mediaFilterRepository.setRecommendation(mediaRecommendation) + mediaFilterRepository.setRecommendationsLoadingState(smartspaceLoadingModel) mediaRecommendationsInteractor.switchToMediaControl(PACKAGE_NAME) mediaFilterRepository.addSelectedUserMediaEntry(data) - mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId)) + mediaFilterRepository.addMediaDataLoadingState(mediaLoadingModel) - assertThat(isMediaFromRec).isFalse() + assertThat(currentMedia) + .containsExactly(MediaCommonModel.MediaRecommendations(smartspaceLoadingModel)) + .inOrder() mediaFilterRepository.addSelectedUserMediaEntry(data.copy(isPlaying = true)) mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId)) - assertThat(isMediaFromRec).isTrue() + assertThat(currentMedia) + .containsExactly( + MediaCommonModel.MediaControl(mediaLoadingModel, isMediaFromRec = true), + MediaCommonModel.MediaRecommendations(smartspaceLoadingModel) + ) + .inOrder() } companion object { private const val KEY_MEDIA_SMARTSPACE = "MEDIA_SMARTSPACE_ID" private const val PACKAGE_NAME = "com.android.example" + private const val KEY = "key" } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt index d9224d7e3421..bd3b77a678db 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaControlInteractorTest.kt @@ -27,12 +27,16 @@ import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.animation.Expandable import com.android.systemui.bluetooth.mockBroadcastDialogController +import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope +import com.android.systemui.media.controls.data.repository.mediaDataRepository import com.android.systemui.media.controls.domain.pipeline.MediaDataFilterImpl +import com.android.systemui.media.controls.domain.pipeline.MediaDataProcessor import com.android.systemui.media.controls.domain.pipeline.interactor.MediaControlInteractor import com.android.systemui.media.controls.domain.pipeline.interactor.mediaControlInteractor import com.android.systemui.media.controls.domain.pipeline.mediaDataFilter +import com.android.systemui.media.controls.domain.pipeline.mediaDataProcessor import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.media.controls.util.mediaInstanceId import com.android.systemui.media.mediaOutputDialogManager @@ -211,6 +215,21 @@ class MediaControlInteractorTest : SysuiTestCase() { ) } + @Test + fun removeMediaControl() { + val listener = mock<MediaDataProcessor.Listener>() + kosmos.mediaDataProcessor.addInternalListener(listener) + + var mediaData = MediaData(userId = USER_ID, instanceId = instanceId, artist = ARTIST) + kosmos.mediaDataRepository.addMediaEntry(KEY, mediaData) + + underTest.removeMediaControl(null, instanceId, 0L) + kosmos.fakeExecutor.advanceClockToNext() + kosmos.fakeExecutor.runAllReady() + + verify(listener).onMediaDataRemoved(eq(KEY), eq(true)) + } + companion object { private const val USER_ID = 0 private const val KEY = "key" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt index d1e475f590dd..4226a9d89ad1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt @@ -92,13 +92,29 @@ class MediaCarouselViewModelTest : SysuiTestCase() { val instanceId1 = InstanceId.fakeInstanceId(123) val instanceId2 = InstanceId.fakeInstanceId(456) - loadMediaControl(KEY, instanceId1) - loadMediaControl(KEY_2, instanceId2) + loadMediaControl(KEY, instanceId1, isPlaying = true) + loadMediaControl(KEY_2, instanceId2, isPlaying = true) + loadMediaControl(KEY, instanceId1, isPlaying = false) - val firstMediaControl = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl - val secondMediaControl = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl - assertThat(firstMediaControl.instanceId).isEqualTo(instanceId2) - assertThat(secondMediaControl.instanceId).isEqualTo(instanceId1) + var mediaControl2 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl + var mediaControl1 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl + assertThat(mediaControl2.instanceId).isEqualTo(instanceId2) + assertThat(mediaControl1.instanceId).isEqualTo(instanceId1) + + loadMediaControl(KEY, instanceId1, isPlaying = true) + loadMediaControl(KEY_2, instanceId2, isPlaying = false) + + mediaControl2 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl + mediaControl1 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl + assertThat(mediaControl2.instanceId).isEqualTo(instanceId2) + assertThat(mediaControl1.instanceId).isEqualTo(instanceId1) + + underTest.onAttached() + + mediaControl1 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl + mediaControl2 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl + assertThat(mediaControl1.instanceId).isEqualTo(instanceId1) + assertThat(mediaControl2.instanceId).isEqualTo(instanceId2) } @Test @@ -147,6 +163,7 @@ class MediaCarouselViewModelTest : SysuiTestCase() { val mediaControl = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl assertThat(sortedMedia).hasSize(2) assertThat(mediaControl.instanceId).isEqualTo(instanceId) + assertThat(mediaControl.isMediaFromRec).isTrue() } private fun loadMediaControl(key: String, instanceId: InstanceId, isPlaying: Boolean = true) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CloseShadeRightAfterClickTestB339290820.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CloseShadeRightAfterClickTestB339290820.kt new file mode 100644 index 000000000000..8d1aa73aa55c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CloseShadeRightAfterClickTestB339290820.kt @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.external + +import android.app.PendingIntent +import android.content.ComponentName +import android.content.Context +import android.content.ContextWrapper +import android.content.Intent +import android.content.ServiceConnection +import android.content.applicationContext +import android.content.packageManager +import android.os.Binder +import android.os.Handler +import android.os.RemoteException +import android.os.UserHandle +import android.platform.test.annotations.EnableFlags +import android.service.quicksettings.Tile +import android.testing.TestableContext +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX +import com.android.systemui.SysuiTestCase +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.kosmos.testCase +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade +import com.android.systemui.qs.tiles.impl.custom.customTileSpec +import com.android.systemui.testKosmos +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.fakeSystemClock +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString + +@RunWith(AndroidJUnit4::class) +@SmallTest +class CloseShadeRightAfterClickTestB339290820 : SysuiTestCase() { + + private val testableContext: TestableContext + private val bindDelayExecutor: FakeExecutor + private val kosmos = + testKosmos().apply { + testableContext = testCase.context + bindDelayExecutor = FakeExecutor(fakeSystemClock) + testableContext.setMockPackageManager(packageManager) + customTileSpec = TileSpec.create(testComponentName) + applicationContext = ContextWrapperDelayedBind(testableContext, bindDelayExecutor) + } + + @Before + fun setUp() { + kosmos.apply { + whenever(packageManager.getPackageUidAsUser(anyString(), anyInt(), anyInt())) + .thenReturn(Binder.getCallingUid()) + packageManagerAdapterFacade.setIsActive(true) + testableContext.addMockService(testComponentName, iQSTileService.asBinder()) + } + } + + @Test + @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX) + fun testStopListeningShortlyAfterClick_clickIsSent() { + with(kosmos) { + val tile = FakeCustomTileInterface(tileServices) + // Flush any bind from startup + FakeExecutor.exhaustExecutors(fakeExecutor, bindDelayExecutor) + + // Open QS + tile.setListening(true) + fakeExecutor.runAllReady() + tile.click() + fakeExecutor.runAllReady() + + // No clicks yet because the latch is preventing the bind + assertThat(iQSTileService.clicks).isEmpty() + + // Close QS + tile.setListening(false) + fakeExecutor.runAllReady() + // And finally bind + FakeExecutor.exhaustExecutors(fakeExecutor, bindDelayExecutor) + + assertThat(iQSTileService.clicks).containsExactly(tile.token) + } + } +} + +private val testComponentName = ComponentName("pkg", "srv") + +// This is a fake `CustomTile` that implements what we need for the test. Mainly setListening and +// click +private class FakeCustomTileInterface(tileServices: TileServices) : CustomTileInterface { + override val user: Int + get() = 0 + override val qsTile: Tile = Tile() + override val component: ComponentName = testComponentName + private var listening = false + private val serviceManager = tileServices.getTileWrapper(this) + private val serviceInterface = serviceManager.tileService + + val token = Binder() + + override fun getTileSpec(): String { + return CustomTile.toSpec(component) + } + + override fun refreshState() {} + + override fun updateTileState(tile: Tile, uid: Int) {} + + override fun onDialogShown() {} + + override fun onDialogHidden() {} + + override fun startActivityAndCollapse(pendingIntent: PendingIntent) {} + + override fun startUnlockAndRun() {} + + fun setListening(listening: Boolean) { + if (listening == this.listening) return + this.listening = listening + + try { + if (listening) { + if (!serviceManager.isActiveTile) { + serviceManager.setBindRequested(true) + serviceInterface.onStartListening() + } + } else { + serviceInterface.onStopListening() + serviceManager.setBindRequested(false) + } + } catch (e: RemoteException) { + // Called through wrapper, won't happen here. + } + } + + fun click() { + try { + if (serviceManager.isActiveTile) { + serviceManager.setBindRequested(true) + serviceInterface.onStartListening() + } + serviceInterface.onClick(token) + } catch (e: RemoteException) { + // Called through wrapper, won't happen here. + } + } +} + +private class ContextWrapperDelayedBind( + val context: Context, + val executor: FakeExecutor, +) : ContextWrapper(context) { + override fun bindServiceAsUser( + service: Intent, + conn: ServiceConnection, + flags: Int, + user: UserHandle + ): Boolean { + executor.execute { super.bindServiceAsUser(service, conn, flags, user) } + return true + } + + override fun bindServiceAsUser( + service: Intent, + conn: ServiceConnection, + flags: BindServiceFlags, + user: UserHandle + ): Boolean { + executor.execute { super.bindServiceAsUser(service, conn, flags, user) } + return true + } + + override fun bindServiceAsUser( + service: Intent?, + conn: ServiceConnection?, + flags: Int, + handler: Handler?, + user: UserHandle? + ): Boolean { + executor.execute { super.bindServiceAsUser(service, conn, flags, handler, user) } + return true + } + + override fun bindServiceAsUser( + service: Intent, + conn: ServiceConnection, + flags: BindServiceFlags, + handler: Handler, + user: UserHandle + ): Boolean { + executor.execute { super.bindServiceAsUser(service, conn, flags, handler, user) } + return true + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt index da60c18dcfd7..dfc004a126bd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt @@ -30,10 +30,10 @@ import com.android.systemui.qs.external.TileServiceKey import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat import com.android.systemui.qs.tiles.impl.custom.commons.copy +import com.android.systemui.qs.tiles.impl.custom.customTileSpec import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade -import com.android.systemui.qs.tiles.impl.custom.tileSpec import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first @@ -47,11 +47,11 @@ import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) class CustomTileRepositoryTest : SysuiTestCase() { - private val kosmos = Kosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) } + private val kosmos = Kosmos().apply { customTileSpec = TileSpec.create(TEST_COMPONENT) } private val underTest: CustomTileRepository = with(kosmos) { CustomTileRepositoryImpl( - tileSpec, + customTileSpec, customTileStatePersister, packageManagerAdapterFacade.packageManagerAdapter, testScope.testScheduler, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt index a5c554406848..a29289a7409a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractorTest.kt @@ -38,8 +38,8 @@ import com.android.systemui.qs.tiles.impl.custom.customTileInteractor import com.android.systemui.qs.tiles.impl.custom.customTilePackagesUpdatesRepository import com.android.systemui.qs.tiles.impl.custom.customTileRepository import com.android.systemui.qs.tiles.impl.custom.customTileServiceInteractor +import com.android.systemui.qs.tiles.impl.custom.customTileSpec import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults -import com.android.systemui.qs.tiles.impl.custom.tileSpec import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.user.data.repository.userRepository @@ -60,12 +60,12 @@ class CustomTileDataInteractorTest : SysuiTestCase() { private val kosmos = testKosmos().apply { componentName = TEST_COMPONENT - tileSpec = TileSpec.create(componentName) + customTileSpec = TileSpec.create(componentName) } private val underTest = with(kosmos) { CustomTileDataInteractor( - tileSpec = tileSpec, + tileSpec = customTileSpec, defaultsRepository = customTileDefaultsRepository, serviceInteractor = customTileServiceInteractor, customTileInteractor = customTileInteractor, @@ -180,7 +180,7 @@ class CustomTileDataInteractorTest : SysuiTestCase() { setup() customTileDefaultsRepository.putDefaults( TEST_USER_1.userHandle, - tileSpec.componentName, + customTileSpec.componentName, CustomTileDefaults.Result(TEST_TILE.icon, TEST_TILE.label), ) @@ -198,7 +198,7 @@ class CustomTileDataInteractorTest : SysuiTestCase() { setup() customTileDefaultsRepository.putDefaults( TEST_USER_1.userHandle, - tileSpec.componentName, + customTileSpec.componentName, CustomTileDefaults.Error, ) @@ -216,7 +216,7 @@ class CustomTileDataInteractorTest : SysuiTestCase() { setup() customTileDefaultsRepository.putDefaults( TEST_USER_2.userHandle, - tileSpec.componentName, + customTileSpec.componentName, CustomTileDefaults.Error, ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt index 9546a32e2a06..33299d9ce74c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt @@ -31,9 +31,9 @@ import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.impl.custom.TileSubject.Companion.assertThat import com.android.systemui.qs.tiles.impl.custom.customTileDefaultsRepository import com.android.systemui.qs.tiles.impl.custom.customTileRepository +import com.android.systemui.qs.tiles.impl.custom.customTileSpec import com.android.systemui.qs.tiles.impl.custom.customTileStatePersister import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults -import com.android.systemui.qs.tiles.impl.custom.tileSpec import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -50,12 +50,12 @@ import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) class CustomTileInteractorTest : SysuiTestCase() { - private val kosmos = testKosmos().apply { tileSpec = TileSpec.create(TEST_COMPONENT) } + private val kosmos = testKosmos().apply { customTileSpec = TileSpec.create(TEST_COMPONENT) } private val underTest: CustomTileInteractor = with(kosmos) { CustomTileInteractor( - tileSpec = tileSpec, + tileSpec = customTileSpec, defaultsRepository = customTileDefaultsRepository, customTileRepository = customTileRepository, tileScope = testScope.backgroundScope, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt index a2127a4717ce..3972938d7b1b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt @@ -33,9 +33,9 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject.Companion.assertThat import com.android.systemui.qs.tiles.impl.custom.customTileQsTileConfig +import com.android.systemui.qs.tiles.impl.custom.customTileSpec import com.android.systemui.qs.tiles.impl.custom.domain.CustomTileMapper import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel -import com.android.systemui.qs.tiles.impl.custom.tileSpec import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any @@ -51,7 +51,8 @@ import org.junit.runner.RunWith class CustomTileMapperTest : SysuiTestCase() { private val uriGrantsManager: IUriGrantsManager = mock {} - private val kosmos = testKosmos().apply { tileSpec = TileSpec.Companion.create(TEST_COMPONENT) } + private val kosmos = + testKosmos().apply { customTileSpec = TileSpec.Companion.create(TEST_COMPONENT) } private val underTest by lazy { CustomTileMapper( context = mock { whenever(createContextAsUser(any(), any())).thenReturn(context) }, @@ -202,7 +203,7 @@ class CustomTileMapperTest : SysuiTestCase() { ) = CustomTileDataModel( UserHandle.of(1), - tileSpec.componentName, + customTileSpec.componentName, Tile().apply { state = tileState label = "test label" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt index c709f16c3213..72e5766e409a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractorTest.kt @@ -44,9 +44,9 @@ import com.android.systemui.qs.tiles.base.actions.pendingIntentInputs import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.longClick import com.android.systemui.qs.tiles.impl.custom.customTileServiceInteractor +import com.android.systemui.qs.tiles.impl.custom.customTileSpec import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import com.android.systemui.qs.tiles.impl.custom.qsTileLogger -import com.android.systemui.qs.tiles.impl.custom.tileSpec import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.any @@ -68,7 +68,7 @@ class CustomTileUserActionInteractorTest : SysuiTestCase() { private val kosmos = testKosmos().apply { componentName = TEST_COMPONENT - tileSpec = TileSpec.create(componentName) + customTileSpec = TileSpec.create(componentName) testCase = this@CustomTileUserActionInteractorTest } @@ -79,7 +79,7 @@ class CustomTileUserActionInteractorTest : SysuiTestCase() { mock { whenever(packageManager).thenReturn(packageManagerFacade.packageManager) }, - tileSpec = tileSpec, + tileSpec = customTileSpec, qsTileLogger = qsTileLogger, windowManager = windowManagerFacade.windowManager, displayTracker = mock {}, @@ -227,7 +227,7 @@ class CustomTileUserActionInteractorTest : SysuiTestCase() { private fun pendingIntent(): PendingIntent = mock { whenever(isActivity).thenReturn(true) } private fun Kosmos.customTileModel( - componentName: ComponentName = tileSpec.componentName, + componentName: ComponentName = customTileSpec.componentName, activityLaunchForClick: PendingIntent? = null, tileState: Int = 111, ) = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractorTest.kt new file mode 100644 index 000000000000..a0aa2d4a9a6c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractorTest.kt @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.night.domain.interactor + +import android.hardware.display.ColorDisplayManager +import android.hardware.display.NightDisplayListener +import android.os.UserHandle +import android.testing.LeakCheck +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.accessibility.data.repository.NightDisplayRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.dagger.NightDisplayListenerModule +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.user.utils.UserScopedService +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.settings.fakeGlobalSettings +import com.android.systemui.util.settings.fakeSettings +import com.android.systemui.util.time.DateFormatUtil +import com.android.systemui.utils.leaks.FakeLocationController +import com.google.common.truth.Truth.assertThat +import java.time.LocalTime +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NightDisplayTileDataInteractorTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val testUser = UserHandle.of(1)!! + private val testStartTime = LocalTime.MIDNIGHT + private val testEndTime = LocalTime.NOON + private val colorDisplayManager = + mock<ColorDisplayManager> { + whenever(nightDisplayAutoMode).thenReturn(ColorDisplayManager.AUTO_MODE_DISABLED) + whenever(isNightDisplayActivated).thenReturn(false) + whenever(nightDisplayCustomStartTime).thenReturn(testStartTime) + whenever(nightDisplayCustomEndTime).thenReturn(testEndTime) + } + private val locationController = FakeLocationController(LeakCheck()) + private val nightDisplayListener = mock<NightDisplayListener>() + private val listenerBuilder = + mock<NightDisplayListenerModule.Builder> { + whenever(setUser(anyInt())).thenReturn(this) + whenever(build()).thenReturn(nightDisplayListener) + } + private val globalSettings = kosmos.fakeGlobalSettings + private val secureSettings = kosmos.fakeSettings + private val dateFormatUtil = mock<DateFormatUtil> { whenever(is24HourFormat).thenReturn(false) } + private val testDispatcher = StandardTestDispatcher() + private val scope = TestScope(testDispatcher) + private val userScopedColorDisplayManager = + mock<UserScopedService<ColorDisplayManager>> { + whenever(forUser(eq(testUser))).thenReturn(colorDisplayManager) + } + private val nightDisplayRepository = + NightDisplayRepository( + testDispatcher, + scope.backgroundScope, + globalSettings, + secureSettings, + listenerBuilder, + userScopedColorDisplayManager, + locationController, + ) + + private val underTest: NightDisplayTileDataInteractor = + NightDisplayTileDataInteractor(context, dateFormatUtil, nightDisplayRepository) + + @Test + fun availability_matchesColorDisplayManager() = runTest { + val availability by collectLastValue(underTest.availability(testUser)) + + val expectedAvailability = ColorDisplayManager.isNightDisplayAvailable(context) + assertThat(availability).isEqualTo(expectedAvailability) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractorTest.kt new file mode 100644 index 000000000000..adc8bcba5a5c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractorTest.kt @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.night.domain.interactor + +import android.hardware.display.ColorDisplayManager +import android.hardware.display.NightDisplayListener +import android.os.UserHandle +import android.provider.Settings +import android.testing.LeakCheck +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.accessibility.data.repository.NightDisplayRepository +import com.android.systemui.dagger.NightDisplayListenerModule +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.actions.intentInputs +import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx +import com.android.systemui.qs.tiles.impl.custom.qsTileLogger +import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel +import com.android.systemui.user.utils.UserScopedService +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.settings.fakeGlobalSettings +import com.android.systemui.util.settings.fakeSettings +import com.android.systemui.utils.leaks.FakeLocationController +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NightDisplayTileUserActionInteractorTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler() + private val testUser = UserHandle.of(1) + private val colorDisplayManager = + mock<ColorDisplayManager> { + whenever(nightDisplayAutoMode).thenReturn(ColorDisplayManager.AUTO_MODE_DISABLED) + whenever(isNightDisplayActivated).thenReturn(false) + } + private val locationController = FakeLocationController(LeakCheck()) + private val nightDisplayListener = mock<NightDisplayListener>() + private val listenerBuilder = + mock<NightDisplayListenerModule.Builder> { + whenever(setUser(ArgumentMatchers.anyInt())).thenReturn(this) + whenever(build()).thenReturn(nightDisplayListener) + } + private val globalSettings = kosmos.fakeGlobalSettings + private val secureSettings = kosmos.fakeSettings + private val testDispatcher = StandardTestDispatcher() + private val scope = TestScope(testDispatcher) + private val userScopedColorDisplayManager = + mock<UserScopedService<ColorDisplayManager>> { + whenever(forUser(eq(testUser))).thenReturn(colorDisplayManager) + } + private val nightDisplayRepository = + NightDisplayRepository( + testDispatcher, + scope.backgroundScope, + globalSettings, + secureSettings, + listenerBuilder, + userScopedColorDisplayManager, + locationController, + ) + + private val underTest = + NightDisplayTileUserActionInteractor( + nightDisplayRepository, + qsTileIntentUserActionHandler, + kosmos.qsTileLogger + ) + + @Test + fun handleClick_inactive_activates() = + scope.runTest { + val startingModel = NightDisplayTileModel.AutoModeOff(false, false) + + underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser)) + + verify(colorDisplayManager).setNightDisplayActivated(true) + } + + @Test + fun handleClick_active_disables() = + scope.runTest { + val startingModel = NightDisplayTileModel.AutoModeOff(true, false) + + underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser)) + + verify(colorDisplayManager).setNightDisplayActivated(false) + } + + @Test + fun handleClick_whenAutoModeTwilight_flipsState() = + scope.runTest { + val originalState = true + val startingModel = NightDisplayTileModel.AutoModeTwilight(originalState, false, false) + + underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser)) + + verify(colorDisplayManager).setNightDisplayActivated(!originalState) + } + + @Test + fun handleClick_whenAutoModeCustom_flipsState() = + scope.runTest { + val originalState = true + val startingModel = + NightDisplayTileModel.AutoModeCustom(originalState, false, null, null, false) + + underTest.handleInput(QSTileInputTestKtx.click(startingModel, testUser)) + + verify(colorDisplayManager).setNightDisplayActivated(!originalState) + } + + @Test + fun handleLongClickWhenEnabled() = + scope.runTest { + val enabledState = true + + underTest.handleInput( + QSTileInputTestKtx.longClick( + NightDisplayTileModel.AutoModeOff(enabledState, false), + testUser + ) + ) + + assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1) + + val intentInput = qsTileIntentUserActionHandler.intentInputs.last() + val actualIntentAction = intentInput.intent.action + val expectedIntentAction = Settings.ACTION_NIGHT_DISPLAY_SETTINGS + assertThat(actualIntentAction).isEqualTo(expectedIntentAction) + } + + @Test + fun handleLongClickWhenDisabled() = + scope.runTest { + val enabledState = false + + underTest.handleInput( + QSTileInputTestKtx.longClick( + NightDisplayTileModel.AutoModeOff(enabledState, false), + testUser + ) + ) + + assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1) + + val intentInput = qsTileIntentUserActionHandler.intentInputs.last() + val actualIntentAction = intentInput.intent.action + val expectedIntentAction = Settings.ACTION_NIGHT_DISPLAY_SETTINGS + assertThat(actualIntentAction).isEqualTo(expectedIntentAction) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt new file mode 100644 index 000000000000..5d2e7013c2f4 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.night.ui + +import android.graphics.drawable.TestStubDrawable +import android.service.quicksettings.Tile +import android.text.TextUtils +import android.widget.Switch +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.qs.tiles.base.logging.QSTileLogger +import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject +import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel +import com.android.systemui.qs.tiles.impl.night.qsNightDisplayTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import com.android.systemui.util.mockito.mock +import java.time.LocalTime +import java.time.format.DateTimeFormatter +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NightDisplayTileMapperTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val config = kosmos.qsNightDisplayTileConfig + + private val testStartTime = LocalTime.MIDNIGHT + private val testEndTime = LocalTime.NOON + + private lateinit var mapper: NightDisplayTileMapper + + @Before + fun setup() { + mapper = + NightDisplayTileMapper( + context.orCreateTestableResources + .apply { + addOverride(R.drawable.qs_nightlight_icon_on, TestStubDrawable()) + addOverride(R.drawable.qs_nightlight_icon_off, TestStubDrawable()) + } + .resources, + context.theme, + mock<QSTileLogger>(), + ) + } + + @Test + fun disabledModel_whenAutoModeOff() { + val inputModel = NightDisplayTileModel.AutoModeOff(false, false) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.INACTIVE, + context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE] + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + /** Force enable does not change the mode by itself. */ + @Test + fun disabledModel_whenAutoModeOff_whenForceEnable() { + val inputModel = NightDisplayTileModel.AutoModeOff(false, true) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.INACTIVE, + context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE] + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun enabledModel_whenAutoModeOff() { + val inputModel = NightDisplayTileModel.AutoModeOff(true, false) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.ACTIVE, + context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE] + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun enabledModel_forceAutoMode_whenAutoModeOff() { + val inputModel = NightDisplayTileModel.AutoModeOff(true, true) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.ACTIVE, + context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE] + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun enabledModel_autoModeTwilight_locationOff() { + val inputModel = NightDisplayTileModel.AutoModeTwilight(true, false, false) + + val outputState = mapper.map(config, inputModel) + + val expectedState = createNightDisplayTileState(QSTileState.ActivationState.ACTIVE, null) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun enabledModel_autoModeTwilight_locationOn() { + val inputModel = NightDisplayTileModel.AutoModeTwilight(true, false, true) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.ACTIVE, + context.getString(R.string.quick_settings_night_secondary_label_until_sunrise) + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun disabledModel_autoModeTwilight_locationOn() { + val inputModel = NightDisplayTileModel.AutoModeTwilight(false, false, true) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.INACTIVE, + context.getString(R.string.quick_settings_night_secondary_label_on_at_sunset) + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun disabledModel_autoModeTwilight_locationOff() { + val inputModel = NightDisplayTileModel.AutoModeTwilight(false, false, false) + + val outputState = mapper.map(config, inputModel) + + val expectedState = createNightDisplayTileState(QSTileState.ActivationState.INACTIVE, null) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun disabledModel_autoModeCustom_24Hour() { + val inputModel = + NightDisplayTileModel.AutoModeCustom(false, false, testStartTime, null, true) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.INACTIVE, + context.getString( + R.string.quick_settings_night_secondary_label_on_at, + formatter24Hour.format(testStartTime) + ) + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun disabledModel_autoModeCustom_12Hour() { + val inputModel = + NightDisplayTileModel.AutoModeCustom(false, false, testStartTime, null, false) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.INACTIVE, + context.getString( + R.string.quick_settings_night_secondary_label_on_at, + formatter12Hour.format(testStartTime) + ) + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + /** Should have the same outcome as [disabledModel_autoModeCustom_12Hour] */ + @Test + fun disabledModel_autoModeCustom_12Hour_isEnrolledForcedAutoMode() { + val inputModel = + NightDisplayTileModel.AutoModeCustom(false, true, testStartTime, null, false) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.INACTIVE, + context.getString( + R.string.quick_settings_night_secondary_label_on_at, + formatter12Hour.format(testStartTime) + ) + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun enabledModel_autoModeCustom_24Hour() { + val inputModel = NightDisplayTileModel.AutoModeCustom(true, false, null, testEndTime, true) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.ACTIVE, + context.getString( + R.string.quick_settings_secondary_label_until, + formatter24Hour.format(testEndTime) + ) + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun enabledModel_autoModeCustom_12Hour() { + val inputModel = NightDisplayTileModel.AutoModeCustom(true, false, null, testEndTime, false) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.ACTIVE, + context.getString( + R.string.quick_settings_secondary_label_until, + formatter12Hour.format(testEndTime) + ) + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + /** Should have the same state as [enabledModel_autoModeCustom_24Hour] */ + @Test + fun enabledModel_autoModeCustom_24Hour_forceEnabled() { + val inputModel = NightDisplayTileModel.AutoModeCustom(true, true, null, testEndTime, true) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createNightDisplayTileState( + QSTileState.ActivationState.ACTIVE, + context.getString( + R.string.quick_settings_secondary_label_until, + formatter24Hour.format(testEndTime) + ) + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + private fun createNightDisplayTileState( + activationState: QSTileState.ActivationState, + secondaryLabel: String? + ): QSTileState { + val label = context.getString(R.string.quick_settings_night_display_label) + + val contentDescription = + if (TextUtils.isEmpty(secondaryLabel)) label + else TextUtils.concat(label, ", ", secondaryLabel) + return QSTileState( + { + Icon.Loaded( + context.getDrawable( + if (activationState == QSTileState.ActivationState.ACTIVE) + R.drawable.qs_nightlight_icon_on + else R.drawable.qs_nightlight_icon_off + )!!, + null + ) + }, + label, + activationState, + secondaryLabel, + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK), + contentDescription, + null, + QSTileState.SideViewIcon.None, + QSTileState.EnabledState.ENABLED, + Switch::class.qualifiedName + ) + } + + private companion object { + val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a") + val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm") + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt index 179ba42cc772..0b55befce932 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt @@ -35,7 +35,10 @@ import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope +import com.android.systemui.media.controls.data.repository.mediaFilterRepository import com.android.systemui.media.controls.domain.pipeline.MediaDataManager +import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor +import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.qs.FooterActionsController import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter @@ -100,7 +103,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { footerActionsViewModelFactory = footerActionsViewModelFactory, footerActionsController = footerActionsController, sceneBackInteractor = sceneBackInteractor, - mediaDataManager = mediaDataManager, + mediaCarouselInteractor = kosmos.mediaCarouselInteractor, ) } @@ -236,20 +239,34 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() { } @Test - fun hasMedia_mediaVisible() { + fun addAndRemoveMedia_mediaVisibilityIsUpdated() = testScope.runTest { - whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true) + kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) + val isMediaVisible by collectLastValue(underTest.isMediaVisible) + val userMedia = MediaData(active = true) - assertThat(underTest.isMediaVisible()).isTrue() + assertThat(isMediaVisible).isFalse() + + kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia) + + assertThat(isMediaVisible).isTrue() + + kosmos.mediaFilterRepository.removeSelectedUserMediaEntry(userMedia.instanceId) + + assertThat(isMediaVisible).isFalse() } - } @Test - fun doesNotHaveMedia_mediaNotVisible() { + fun addInactiveMedia_mediaVisibilityIsUpdated() = testScope.runTest { - whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(false) + kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) + val isMediaVisible by collectLastValue(underTest.isMediaVisible) + val userMedia = MediaData(active = false) + + assertThat(isMediaVisible).isFalse() - assertThat(underTest.isMediaVisible()).isFalse() + kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia) + + assertThat(isMediaVisible).isTrue() } - } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 4cb8bf84967b..9e7e766cb820 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -52,6 +52,7 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel import com.android.systemui.kosmos.testScope import com.android.systemui.media.controls.domain.pipeline.MediaDataManager +import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.powerInteractor @@ -209,7 +210,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { qsSceneAdapter = qsFlexiglassAdapter, notifications = kosmos.notificationsPlaceholderViewModel, brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel, - mediaDataManager = mediaDataManager, + mediaCarouselInteractor = kosmos.mediaCarouselInteractor, shadeInteractor = kosmos.shadeInteractor, footerActionsController = kosmos.footerActionsController, footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory, 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 93465c804d0e..677477df8ea1 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 @@ -437,12 +437,12 @@ class SceneContainerStartableTest : SysuiTestCase() { runCurrent() assertThat( sysUiState.flags and - QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0 + QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0L ) .isTrue() assertThat( sysUiState.flags and - QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0 + QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0L ) .isFalse() @@ -450,12 +450,12 @@ class SceneContainerStartableTest : SysuiTestCase() { runCurrent() assertThat( sysUiState.flags and - QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0 + QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED != 0L ) .isFalse() assertThat( sysUiState.flags and - QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0 + QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING != 0L ) .isTrue() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt new file mode 100644 index 000000000000..545a0c70719f --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModelTest.kt @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.scene.ui.viewmodel + +import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.SwipeDirection +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.scene.shared.model.TransitionKeys.ToSplitShade +import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.shade.shared.model.ShadeMode +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper +@EnableSceneContainer +class GoneSceneViewModelTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val shadeRepository by lazy { kosmos.shadeRepository } + private lateinit var underTest: GoneSceneViewModel + + @Before + fun setUp() { + underTest = + GoneSceneViewModel( + applicationScope = testScope.backgroundScope, + shadeInteractor = kosmos.shadeInteractor, + ) + } + + @Test + fun downTransitionKey_splitShadeEnabled_isGoneToSplitShade() = + testScope.runTest { + val destinationScenes by collectLastValue(underTest.destinationScenes) + shadeRepository.setShadeMode(ShadeMode.Split) + runCurrent() + + assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.transitionKey) + .isEqualTo(ToSplitShade) + } + + @Test + fun downTransitionKey_splitShadeDisabled_isNull() = + testScope.runTest { + val destinationScenes by collectLastValue(underTest.destinationScenes) + shadeRepository.setShadeMode(ShadeMode.Single) + runCurrent() + + assertThat(destinationScenes?.get(Swipe(SwipeDirection.Down))?.transitionKey).isNull() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt index aa0ca186c1ee..78c4def5689b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt @@ -60,7 +60,7 @@ import platform.test.runner.parameterized.Parameters @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class ShadeInteractorImplTest(flags: FlagsParameterization?) : SysuiTestCase() { +class ShadeInteractorImplTest(flags: FlagsParameterization) : SysuiTestCase() { val kosmos = testKosmos() val testScope = kosmos.testScope val configurationRepository by lazy { kosmos.fakeConfigurationRepository } @@ -85,7 +85,7 @@ class ShadeInteractorImplTest(flags: FlagsParameterization?) : SysuiTestCase() { } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt index 44c9695d8b03..cecc70cae60d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt @@ -60,7 +60,7 @@ import platform.test.runner.parameterized.Parameters @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class ShadeStartableTest(flags: FlagsParameterization?) : SysuiTestCase() { +class ShadeStartableTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val shadeInteractor by lazy { kosmos.shadeInteractor } @@ -80,7 +80,7 @@ class ShadeStartableTest(flags: FlagsParameterization?) : SysuiTestCase() { } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index 5312ad809a72..482dc5d992f0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -32,13 +32,17 @@ import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope +import com.android.systemui.media.controls.data.repository.mediaFilterRepository import com.android.systemui.media.controls.domain.pipeline.MediaDataManager +import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor +import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.qs.footerActionsController import com.android.systemui.qs.footerActionsViewModelFactory import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.domain.interactor.shadeInteractor @@ -49,7 +53,6 @@ import com.android.systemui.testKosmos import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import java.util.Locale import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -93,7 +96,7 @@ class ShadeSceneViewModelTest : SysuiTestCase() { qsSceneAdapter = qsSceneAdapter, notifications = kosmos.notificationsPlaceholderViewModel, brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel, - mediaDataManager = mediaDataManager, + mediaCarouselInteractor = kosmos.mediaCarouselInteractor, shadeInteractor = kosmos.shadeInteractor, footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory, footerActionsController = kosmos.footerActionsController, @@ -159,6 +162,27 @@ class ShadeSceneViewModelTest : SysuiTestCase() { } @Test + fun upTransitionKey_splitShadeEnabled_isGoneToSplitShade() = + testScope.runTest { + val destinationScenes by collectLastValue(underTest.destinationScenes) + shadeRepository.setShadeMode(ShadeMode.Split) + runCurrent() + + assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.transitionKey) + .isEqualTo(ToSplitShade) + } + + @Test + fun upTransitionKey_splitShadeDisable_isNull() = + testScope.runTest { + val destinationScenes by collectLastValue(underTest.destinationScenes) + shadeRepository.setShadeMode(ShadeMode.Single) + runCurrent() + + assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.transitionKey).isNull() + } + + @Test fun isClickable_deviceUnlocked_false() = testScope.runTest { val isClickable by collectLastValue(underTest.isClickable) @@ -200,19 +224,20 @@ class ShadeSceneViewModelTest : SysuiTestCase() { } @Test - fun hasActiveMedia_mediaVisible() = + fun addAndRemoveMedia_mediaVisibilityisUpdated() = testScope.runTest { - whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true) + val isMediaVisible by collectLastValue(underTest.isMediaVisible) + val userMedia = MediaData(active = true) - assertThat(underTest.isMediaVisible()).isTrue() - } + assertThat(isMediaVisible).isFalse() - @Test - fun doesNotHaveActiveMedia_mediaNotVisible() = - testScope.runTest { - whenever(mediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(false) + kosmos.mediaFilterRepository.addSelectedUserMediaEntry(userMedia) + + assertThat(isMediaVisible).isTrue() + + kosmos.mediaFilterRepository.removeSelectedUserMediaEntry(userMedia.instanceId) - assertThat(underTest.isMediaVisible()).isFalse() + assertThat(isMediaVisible).isFalse() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt new file mode 100644 index 000000000000..8cb811de851b --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.service.notification.NotificationListenerService +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.dump.dumpManager +import com.android.systemui.media.controls.domain.pipeline.MediaDataManager +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.mockNotifCollection +import com.android.systemui.statusbar.notification.collection.notifPipeline +import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider +import com.android.systemui.testKosmos +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NotificationMediaManagerTest : SysuiTestCase() { + + private val KEY = "KEY" + + private val kosmos = testKosmos() + private val visibilityProvider = kosmos.notificationVisibilityProvider + private val notifPipeline = kosmos.notifPipeline + private val notifCollection = kosmos.mockNotifCollection + private val dumpManager = kosmos.dumpManager + private val mediaDataManager = mock<MediaDataManager>() + private val backgroundExecutor = FakeExecutor(FakeSystemClock()) + + private var listenerCaptor = argumentCaptor<MediaDataManager.Listener>() + + private lateinit var notificationMediaManager: NotificationMediaManager + + @Before + fun setup() { + notificationMediaManager = + NotificationMediaManager( + context, + visibilityProvider, + notifPipeline, + notifCollection, + mediaDataManager, + dumpManager, + backgroundExecutor, + ) + + verify(mediaDataManager).addListener(listenerCaptor.capture()) + } + + @Test + @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS) + fun mediaDataRemoved_userInitiated_dismissNotif() { + val notifEntryCaptor = argumentCaptor<NotificationEntry>() + val notifEntry = mock<NotificationEntry>() + whenever(notifEntry.key).thenReturn(KEY) + whenever(notifEntry.ranking).thenReturn(NotificationListenerService.Ranking()) + whenever(notifPipeline.allNotifs).thenReturn(listOf(notifEntry)) + + listenerCaptor.lastValue.onMediaDataRemoved(KEY, true) + + verify(notifCollection).dismissNotification(notifEntryCaptor.capture(), any()) + assertThat(notifEntryCaptor.lastValue.key).isEqualTo(KEY) + } + + @Test + @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS) + fun mediaDataRemoved_notUserInitiated_doesNotDismissNotif() { + listenerCaptor.lastValue.onMediaDataRemoved(KEY, false) + + verify(notifCollection, never()).dismissNotification(any(), any()) + } + + @Test + @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_USER_INITIATED_DISMISS) + fun mediaDataRemoved_notUserInitiated_flagOff_dismissNotif() { + val notifEntryCaptor = argumentCaptor<NotificationEntry>() + val notifEntry = mock<NotificationEntry>() + whenever(notifEntry.key).thenReturn(KEY) + whenever(notifEntry.ranking).thenReturn(NotificationListenerService.Ranking()) + whenever(notifPipeline.allNotifs).thenReturn(listOf(notifEntry)) + + listenerCaptor.lastValue.onMediaDataRemoved(KEY, false) + + verify(notifCollection).dismissNotification(notifEntryCaptor.capture(), any()) + assertThat(notifEntryCaptor.lastValue.key).isEqualTo(KEY) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index d353a625b890..f06e04b70809 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -33,7 +33,10 @@ import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.parameterizeSceneContainerFlag import com.android.systemui.jank.interactionJankMonitor import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -65,11 +68,11 @@ import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) @TestableLooper.RunWithLooper -class StatusBarStateControllerImplTest(flags: FlagsParameterization?) : SysuiTestCase() { +class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - + private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository private val mockDarkAnimator = mock<ObjectAnimator>() private lateinit var underTest: StatusBarStateControllerImpl @@ -84,7 +87,7 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization?) : SysuiTes } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before @@ -98,6 +101,7 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization?) : SysuiTes uiEventLogger, { kosmos.interactionJankMonitor }, JavaAdapter(testScope.backgroundScope), + { kosmos.keyguardTransitionInteractor }, { kosmos.shadeInteractor }, { kosmos.deviceUnlockedInteractor }, { kosmos.sceneInteractor }, @@ -330,4 +334,25 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization?) : SysuiTes assertThat(currentScene).isEqualTo(Scenes.QuickSettings) assertThat(statusBarState).isEqualTo(StatusBarState.SHADE) } + + @Test + fun leaveOpenOnKeyguard_whenGone_isFalse() = + testScope.runTest { + underTest.start() + underTest.setLeaveOpenOnKeyguardHide(true) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + testScope = testScope, + ) + assertThat(underTest.leaveOpenOnKeyguardHide()).isEqualTo(true) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + testScope = testScope, + ) + assertThat(underTest.leaveOpenOnKeyguardHide()).isEqualTo(false) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt index cbbc4d897048..9367a93a2890 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt @@ -59,7 +59,7 @@ import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterization?) : +class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { companion object { @@ -71,7 +71,7 @@ class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterizati } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } private val kosmos = testKosmos() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt index 7ac549a7b551..cc5df74e5e6e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt @@ -20,12 +20,13 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import android.app.NotificationManager.Policy import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.FlagsParameterization import android.provider.Settings -import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags +import com.android.systemui.flags.andSceneContainer import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.shared.model.StatusBarState @@ -33,7 +34,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.power.shared.model.WakefulnessState import com.android.systemui.res.R -import com.android.systemui.shade.data.repository.fakeShadeRepository +import com.android.systemui.shade.shadeTestUtil import com.android.systemui.statusbar.data.repository.fakeRemoteInputRepository import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository @@ -56,11 +57,13 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(AndroidJUnit4::class) +@RunWith(ParameterizedAndroidJunit4::class) @EnableFlags(FooterViewRefactor.FLAG_NAME) -class NotificationListViewModelTest : SysuiTestCase() { +class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos().apply { fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } @@ -72,16 +75,30 @@ class NotificationListViewModelTest : SysuiTestCase() { private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository private val fakePowerRepository = kosmos.fakePowerRepository private val fakeRemoteInputRepository = kosmos.fakeRemoteInputRepository - private val fakeShadeRepository = kosmos.fakeShadeRepository private val fakeUserSetupRepository = kosmos.fakeUserSetupRepository private val headsUpRepository = kosmos.headsUpNotificationRepository private val zenModeRepository = kosmos.zenModeRepository - val underTest = kosmos.notificationListViewModel + private val shadeTestUtil by lazy { kosmos.shadeTestUtil } + + private lateinit var underTest: NotificationListViewModel + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf().andSceneContainer() + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } @Before fun setUp() { MockitoAnnotations.initMocks(this) + underTest = kosmos.notificationListViewModel } @Test @@ -163,7 +180,7 @@ class NotificationListViewModelTest : SysuiTestCase() { // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) // AND quick settings are expanded - fakeShadeRepository.legacyQsFullscreen.value = true + shadeTestUtil.setQsFullscreen(true) runCurrent() // THEN empty shade is not visible @@ -178,9 +195,10 @@ class NotificationListViewModelTest : SysuiTestCase() { // WHEN has no notifs activeNotificationListRepository.setActiveNotifs(count = 0) // AND quick settings are expanded - fakeShadeRepository.setQsExpansion(1f) - // AND split shade is enabled + shadeTestUtil.setQsExpansion(1f) + // AND split shade is expanded overrideResource(R.bool.config_use_split_notification_shade, true) + shadeTestUtil.setShadeExpansion(1f) fakeConfigurationController.notifyConfigurationChanged() runCurrent() @@ -290,7 +308,7 @@ class NotificationListViewModelTest : SysuiTestCase() { activeNotificationListRepository.setActiveNotifs(count = 2) // AND shade is open fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - fakeShadeRepository.setLegacyShadeExpansion(1f) + shadeTestUtil.setShadeExpansion(1f) runCurrent() // THEN footer is visible @@ -306,7 +324,7 @@ class NotificationListViewModelTest : SysuiTestCase() { activeNotificationListRepository.setActiveNotifs(count = 2) // AND shade is open on lockscreen fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED) - fakeShadeRepository.setLegacyShadeExpansion(1f) + shadeTestUtil.setShadeExpansion(1f) runCurrent() // THEN footer is visible @@ -337,7 +355,7 @@ class NotificationListViewModelTest : SysuiTestCase() { activeNotificationListRepository.setActiveNotifs(count = 2) // AND shade is open fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - fakeShadeRepository.setLegacyShadeExpansion(1f) + shadeTestUtil.setShadeExpansion(1f) // AND user is not set up fakeUserSetupRepository.setUserSetUp(false) runCurrent() @@ -355,7 +373,7 @@ class NotificationListViewModelTest : SysuiTestCase() { activeNotificationListRepository.setActiveNotifs(count = 2) // AND shade is open fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - fakeShadeRepository.setLegacyShadeExpansion(1f) + shadeTestUtil.setShadeExpansion(1f) // AND device is starting to go to sleep fakePowerRepository.updateWakefulness(WakefulnessState.STARTING_TO_SLEEP) runCurrent() @@ -373,10 +391,10 @@ class NotificationListViewModelTest : SysuiTestCase() { activeNotificationListRepository.setActiveNotifs(count = 2) // AND shade is open fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - fakeShadeRepository.setLegacyShadeExpansion(1f) + shadeTestUtil.setShadeExpansion(1f) // AND quick settings are expanded - fakeShadeRepository.setQsExpansion(1f) - fakeShadeRepository.legacyQsFullscreen.value = true + shadeTestUtil.setQsExpansion(1f) + shadeTestUtil.setQsFullscreen(true) runCurrent() // THEN footer is not visible @@ -390,11 +408,11 @@ class NotificationListViewModelTest : SysuiTestCase() { // WHEN has notifs activeNotificationListRepository.setActiveNotifs(count = 2) + // AND quick settings are expanded + shadeTestUtil.setQsExpansion(1f) // AND shade is open fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - fakeShadeRepository.setLegacyShadeExpansion(1f) - // AND quick settings are expanded - fakeShadeRepository.setQsExpansion(1f) + shadeTestUtil.setShadeExpansion(1f) // AND split shade is enabled overrideResource(R.bool.config_use_split_notification_shade, true) fakeConfigurationController.notifyConfigurationChanged() @@ -413,7 +431,7 @@ class NotificationListViewModelTest : SysuiTestCase() { activeNotificationListRepository.setActiveNotifs(count = 2) // AND shade is open fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - fakeShadeRepository.setLegacyShadeExpansion(1f) + shadeTestUtil.setShadeExpansion(1f) // AND remote input is active fakeRemoteInputRepository.isRemoteInputActive.value = true runCurrent() @@ -431,7 +449,7 @@ class NotificationListViewModelTest : SysuiTestCase() { activeNotificationListRepository.setActiveNotifs(count = 2) // AND shade is open and fully expanded fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - fakeShadeRepository.setLegacyShadeExpansion(1f) + shadeTestUtil.setShadeExpansion(1f) runCurrent() // THEN footer visibility animates @@ -447,7 +465,7 @@ class NotificationListViewModelTest : SysuiTestCase() { activeNotificationListRepository.setActiveNotifs(count = 2) // AND we are on the keyguard fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) - fakeShadeRepository.setLegacyShadeExpansion(1f) + shadeTestUtil.setShadeExpansion(1f) runCurrent() // THEN footer visibility does not animate @@ -461,7 +479,7 @@ class NotificationListViewModelTest : SysuiTestCase() { // WHEN shade is closed fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - fakeShadeRepository.setLegacyShadeExpansion(0f) + shadeTestUtil.setShadeExpansion(0f) runCurrent() // THEN footer is hidden @@ -475,7 +493,7 @@ class NotificationListViewModelTest : SysuiTestCase() { // WHEN shade is open fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - fakeShadeRepository.setLegacyShadeExpansion(1f) + shadeTestUtil.setShadeExpansion(1f) runCurrent() // THEN footer is hidden @@ -489,8 +507,8 @@ class NotificationListViewModelTest : SysuiTestCase() { // WHEN QS partially open fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - fakeShadeRepository.setQsExpansion(0.5f) - fakeShadeRepository.setLegacyShadeExpansion(0.5f) + shadeTestUtil.setQsExpansion(0.5f) + shadeTestUtil.setShadeExpansion(0.5f) runCurrent() // THEN footer is hidden @@ -588,7 +606,7 @@ class NotificationListViewModelTest : SysuiTestCase() { testScope.runTest { val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled) - fakeShadeRepository.setQsExpansion(0.0f) + shadeTestUtil.setQsExpansion(0.0f) fakeKeyguardRepository.setKeyguardShowing(false) runCurrent() @@ -601,7 +619,7 @@ class NotificationListViewModelTest : SysuiTestCase() { testScope.runTest { val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled) - fakeShadeRepository.setQsExpansion(0.0f) + shadeTestUtil.setQsExpansion(0.0f) fakeKeyguardRepository.setKeyguardShowing(true) runCurrent() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index 2cd295c6045d..f2ce745ed293 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -75,7 +75,7 @@ import platform.test.runner.parameterized.Parameters @RunWith(ParameterizedAndroidJunit4::class) // SharedNotificationContainerViewModel is only bound when FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT is on @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) -class SharedNotificationContainerViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { +class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { companion object { @JvmStatic @@ -89,7 +89,7 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization?) : } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } val aodBurnInViewModel = mock(AodBurnInViewModel::class.java) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt index 8ce50379fe5c..63f19fbdfed9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt @@ -249,22 +249,40 @@ class AvalancheControllerTest : SysuiTestCase() { @Test - fun testDelete_showingEntryKeyBecomesPreviousHunKey() { + fun testDelete_deleteSecondToLastEntry_showingEntryKeyBecomesPreviousHunKey() { mAvalancheController.previousHunKey = "" // Entry is showing - val showingEntry = createHeadsUpEntry(id = 0) - mAvalancheController.headsUpEntryShowing = showingEntry + val firstEntry = createHeadsUpEntry(id = 0) + mAvalancheController.headsUpEntryShowing = firstEntry // There's another entry waiting to show next - val nextEntry = createHeadsUpEntry(id = 1) - mAvalancheController.addToNext(nextEntry, runnableMock!!) + val secondEntry = createHeadsUpEntry(id = 1) + mAvalancheController.addToNext(secondEntry, runnableMock!!) // Delete - mAvalancheController.delete(showingEntry, runnableMock, "testLabel") + mAvalancheController.delete(firstEntry, runnableMock, "testLabel") + + // Next entry is shown + assertThat(mAvalancheController.previousHunKey).isEqualTo(firstEntry.mEntry!!.key) + } + + @Test + fun testDelete_deleteLastEntry_previousHunKeyCleared() { + mAvalancheController.previousHunKey = "key" + + // Nothing waiting to show + mAvalancheController.clearNext() + + // One entry is showing + val showingEntry = createHeadsUpEntry(id = 0) + mAvalancheController.headsUpEntryShowing = showingEntry + + // Delete + mAvalancheController.delete(showingEntry, runnableMock!!, "testLabel") // Next entry is shown - assertThat(mAvalancheController.previousHunKey).isEqualTo(showingEntry.mEntry!!.key) + assertThat(mAvalancheController.previousHunKey).isEqualTo(""); } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt index 27a813fb149e..fdea5a4bd765 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelTest.kt @@ -32,7 +32,7 @@ import com.android.systemui.testKosmos import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.volume.panel.ui.VolumePanelUiEvent -import com.android.systemui.volume.panel.volumePanelViewModel +import com.android.systemui.volume.panel.ui.viewmodel.volumePanelViewModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt index 6256eece65dd..ab184abdc963 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorImplTest.kt @@ -22,14 +22,14 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope -import com.android.systemui.volume.panel.availableCriteria -import com.android.systemui.volume.panel.criteriaByKey -import com.android.systemui.volume.panel.defaultCriteria +import com.android.systemui.volume.panel.domain.availableCriteria +import com.android.systemui.volume.panel.domain.defaultCriteria import com.android.systemui.volume.panel.domain.model.ComponentModel -import com.android.systemui.volume.panel.enabledComponents +import com.android.systemui.volume.panel.domain.unavailableCriteria import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey -import com.android.systemui.volume.panel.unavailableCriteria +import com.android.systemui.volume.panel.ui.composable.enabledComponents import com.google.common.truth.Truth.assertThat +import javax.inject.Provider import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith @@ -47,7 +47,7 @@ class ComponentsInteractorImplTest : SysuiTestCase() { with(kosmos) { ComponentsInteractorImpl( enabledComponents, - defaultCriteria, + { defaultCriteria }, testScope.backgroundScope, criteriaByKey, ) @@ -66,9 +66,9 @@ class ComponentsInteractorImplTest : SysuiTestCase() { ) criteriaByKey = mapOf( - BOTTOM_BAR to availableCriteria, - COMPONENT_1 to unavailableCriteria, - COMPONENT_2 to availableCriteria, + BOTTOM_BAR to Provider { availableCriteria }, + COMPONENT_1 to Provider { unavailableCriteria }, + COMPONENT_2 to Provider { availableCriteria }, ) initUnderTest() @@ -96,8 +96,8 @@ class ComponentsInteractorImplTest : SysuiTestCase() { ) criteriaByKey = mapOf( - BOTTOM_BAR to availableCriteria, - COMPONENT_2 to availableCriteria, + BOTTOM_BAR to Provider { availableCriteria }, + COMPONENT_2 to Provider { availableCriteria }, ) defaultCriteria = unavailableCriteria initUnderTest() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt index 3dbf23e3dc58..e3dc55203286 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryTest.kt @@ -20,9 +20,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.testKosmos -import com.android.systemui.volume.panel.componentByKey -import com.android.systemui.volume.panel.mockVolumePanelUiComponentProvider import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey +import com.android.systemui.volume.panel.shared.model.mockVolumePanelUiComponentProvider import com.google.common.truth.Truth import org.junit.Test import org.junit.runner.RunWith diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt index 82ce6d785e90..b37184dc941c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/DefaultComponentsLayoutManagerTest.kt @@ -20,8 +20,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.testKosmos -import com.android.systemui.volume.panel.mockVolumePanelUiComponent import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey +import com.android.systemui.volume.panel.shared.model.mockVolumePanelUiComponent import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager import com.google.common.truth.Truth diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt index 4e0685556145..f6ada4c166dc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelTest.kt @@ -28,15 +28,15 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.policy.fakeConfigurationController import com.android.systemui.testKosmos -import com.android.systemui.volume.panel.componentByKey -import com.android.systemui.volume.panel.componentsLayoutManager -import com.android.systemui.volume.panel.criteriaByKey -import com.android.systemui.volume.panel.mockVolumePanelUiComponentProvider +import com.android.systemui.volume.panel.domain.interactor.criteriaByKey +import com.android.systemui.volume.panel.domain.unavailableCriteria import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey +import com.android.systemui.volume.panel.shared.model.mockVolumePanelUiComponentProvider +import com.android.systemui.volume.panel.ui.composable.componentByKey import com.android.systemui.volume.panel.ui.layout.DefaultComponentsLayoutManager -import com.android.systemui.volume.panel.unavailableCriteria -import com.android.systemui.volume.panel.volumePanelViewModel +import com.android.systemui.volume.panel.ui.layout.componentsLayoutManager import com.google.common.truth.Truth.assertThat +import javax.inject.Provider import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -95,7 +95,7 @@ class VolumePanelViewModelTest : SysuiTestCase() { COMPONENT_2 to mockVolumePanelUiComponentProvider, BOTTOM_BAR to mockVolumePanelUiComponentProvider, ) - criteriaByKey = mapOf(COMPONENT_2 to unavailableCriteria) + criteriaByKey = mapOf(COMPONENT_2 to Provider { unavailableCriteria }) }) { testScope.runTest { val componentsLayout by collectLastValue(underTest.componentsLayout) diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt index 79bf5f19997b..629c96c0ef44 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt @@ -109,6 +109,12 @@ data class ClockMessageBuffers( val largeClockMessageBuffer: MessageBuffer, ) +data class AodClockBurnInModel( + val scale: Float, + val translationX: Float, + val translationY: Float, +) + /** Specifies layout information for the */ interface ClockFaceLayout { /** All clock views to add to the root constraint layout before applying constraints. */ @@ -118,6 +124,8 @@ interface ClockFaceLayout { fun applyConstraints(constraints: ConstraintSet): ConstraintSet fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet + + fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel) } /** A ClockFaceLayout that applies the default lockscreen layout to a single view */ @@ -137,6 +145,10 @@ class DefaultClockFaceLayout(val view: View) : ClockFaceLayout { override fun applyPreviewConstraints(constraints: ConstraintSet): ConstraintSet { return constraints } + + override fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel) { + // Default clock doesn't need detailed control of view + } } /** Events that should call when various rendering parameters change */ diff --git a/packages/SystemUI/res-keyguard/values-hr/strings.xml b/packages/SystemUI/res-keyguard/values-hr/strings.xml index b4b2a1935393..efc750b53cc3 100644 --- a/packages/SystemUI/res-keyguard/values-hr/strings.xml +++ b/packages/SystemUI/res-keyguard/values-hr/strings.xml @@ -61,7 +61,7 @@ <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"Lice nije prepoznato"</string> <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"Pokušajte ponovno ili unesite PIN"</string> <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"Pokušajte ponovno ili unesite zaporku"</string> - <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"Pokušajte ponovno ili izradite uzorak"</string> + <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"Pokušajte ponovno ili nacrtajte uzorak"</string> <string name="kg_bio_too_many_attempts_pin" msgid="5850845723433047605">"PIN je obavezan nakon previše pokušaja"</string> <string name="kg_bio_too_many_attempts_password" msgid="5551690347827728042">"Zaporka je obavezna nakon previše pokušaja"</string> <string name="kg_bio_too_many_attempts_pattern" msgid="736884689355181602">"Uzorak je obavezan nakon previše pokušaja"</string> diff --git a/packages/SystemUI/res-keyguard/values-my/strings.xml b/packages/SystemUI/res-keyguard/values-my/strings.xml index c4fc5f7e0802..2bace4a702eb 100644 --- a/packages/SystemUI/res-keyguard/values-my/strings.xml +++ b/packages/SystemUI/res-keyguard/values-my/strings.xml @@ -116,7 +116,7 @@ <string name="kg_prompt_added_security_password" msgid="6053156069765029006">"ထပ်ဆောင်း လုံခြုံရေးအတွက် စကားဝှက် လိုအပ်သည်"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"စက်ပစ္စည်းကို စီမံခန့်ခွဲသူက လော့ခ်ချထားပါသည်"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"စက်ပစ္စည်းကို ကိုယ်တိုင်ကိုယ်ကျ လော့ခ်ချထားခဲ့သည်"</string> - <string name="kg_face_not_recognized" msgid="7903950626744419160">"မသိ"</string> + <string name="kg_face_not_recognized" msgid="7903950626744419160">"မသိပါ"</string> <string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"‘မျက်နှာပြ လော့ခ်ဖွင့်ခြင်း’ သုံးရန် ‘ဆက်တင်များ’ တွင်ကင်မရာသုံးခွင့်ကိုဖွင့်ပါ"</string> <string name="kg_password_default_pin_message" msgid="1434544655827987873">"{count,plural, =1{ဆင်းမ်ပင်နံပါတ် ထည့်သွင်းပါ။ သင့်စက်ကို လော့ခ်ဖွင့်ရန် မိုဘိုင်းဖုန်းကုမ္ပဏီသို့ မဆက်သွယ်မီ # ကြိမ် ကြိုးစားခွင့်ရှိသေးသည်။}other{ဆင်းမ်ပင်နံပါတ် ထည့်သွင်းပါ။ သင့်တွင် # ကြိမ် ကြိုးစားခွင့်ရှိသေးသည်။}}"</string> <string name="kg_password_default_puk_message" msgid="1025139786449741950">"{count,plural, =1{ဆင်းမ်ကတ်သည် ယခု ပိတ်သွားပါပြီ။ ရှေ့ဆက်ရန် PUK ကုဒ်ကို ထည့်ပါ။ ဆင်းမ်ကတ် အပြီးပိတ်မသွားမီ သင့်တွင် # ကြိမ် ကြိုးစားခွင့်ရှိသေးသည်။ အသေးစိတ်အတွက် မိုဘိုင်းဖုန်းကုမ္ပဏီကို ဆက်သွယ်ပါ။}other{ဆင်းမ်ကတ်သည် ယခု ပိတ်သွားပါပြီ။ ရှေ့ဆက်ရန် PUK ကုဒ်ကို ထည့်ပါ။ ဆင်းမ်ကတ် အပြီးပိတ်မသွားမီ သင့်တွင် # ကြိမ် ကြိုးစားခွင့်ရှိသေးသည်။ အသေးစိတ်အတွက် မိုဘိုင်းဖုန်းကုမ္ပဏီကို ဆက်သွယ်ပါ။}}"</string> diff --git a/packages/SystemUI/res-keyguard/values-ne/strings.xml b/packages/SystemUI/res-keyguard/values-ne/strings.xml index 2bfdef6ca182..224f1aeef05d 100644 --- a/packages/SystemUI/res-keyguard/values-ne/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ne/strings.xml @@ -58,7 +58,7 @@ <string name="kg_wrong_pin_try_again" msgid="3129729383303430190">"PIN मिलेन। फेरि प्रयास गर्नुहोस्।"</string> <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"वा फिंगरप्रिन्ट प्रयोग गरी अनलक गर्नुहोस्"</string> <string name="kg_fp_not_recognized" msgid="5183108260932029241">"फिंगरप्रिन्ट मिलेन"</string> - <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"अनुहार पहिचान गर्न सकिएन"</string> + <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"अनुहार मिलेन"</string> <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"फेरि प्रयास गर्नुहोस् वा PIN हाल्नुहोस्"</string> <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"फेरि प्रयास गर्नुहोस् वा पासवर्ड हाल्नुहोस्"</string> <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"फेरि प्रयास गर्नुहोस् वा प्याटर्न हाल्नुहोस्"</string> diff --git a/packages/SystemUI/res-keyguard/values-sk/strings.xml b/packages/SystemUI/res-keyguard/values-sk/strings.xml index 9b0647afb614..4dffd971dd27 100644 --- a/packages/SystemUI/res-keyguard/values-sk/strings.xml +++ b/packages/SystemUI/res-keyguard/values-sk/strings.xml @@ -57,7 +57,7 @@ <string name="kg_wrong_pin" msgid="4160978845968732624">"Nesprávny kód PIN"</string> <string name="kg_wrong_pin_try_again" msgid="3129729383303430190">"Nesprávny kód PIN. Zopakujte."</string> <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"Alebo odomknite odtlačkom prsta"</string> - <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Nerozpoz. odtlačok prsta"</string> + <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Odtlačok prsta nebol rozpoznaný"</string> <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"Tvár nebola rozpoznaná"</string> <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"Skúste to znova alebo zadajte PIN"</string> <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"Skúste to znova alebo zadajte heslo"</string> diff --git a/packages/SystemUI/res-keyguard/values-ta/strings.xml b/packages/SystemUI/res-keyguard/values-ta/strings.xml index 60f60c4c1879..b73372f72929 100644 --- a/packages/SystemUI/res-keyguard/values-ta/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ta/strings.xml @@ -57,7 +57,7 @@ <string name="kg_wrong_pin" msgid="4160978845968732624">"தவறான பின்"</string> <string name="kg_wrong_pin_try_again" msgid="3129729383303430190">"தவறு. மீண்டும் முயலவும்."</string> <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"இல்லையெனில் கைரேகை மூலம் அன்லாக் செய்யவும்"</string> - <string name="kg_fp_not_recognized" msgid="5183108260932029241">"கைரேகை அடையாளம் இல்லை"</string> + <string name="kg_fp_not_recognized" msgid="5183108260932029241">"கைரேகையை அடையாளம் காண முடியவில்லை"</string> <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"முகம் கண்டறிய முடியவில்லை"</string> <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"மீண்டும் முயலவும் அல்லது பின்னை உள்ளிடவும்"</string> <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"மீண்டும் முயலவும் அல்லது கடவுச்சொல்லை உள்ளிடவும்"</string> diff --git a/packages/SystemUI/res-keyguard/values-ur/strings.xml b/packages/SystemUI/res-keyguard/values-ur/strings.xml index fa2a234d4368..fcb3a3ec8edc 100644 --- a/packages/SystemUI/res-keyguard/values-ur/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ur/strings.xml @@ -116,7 +116,7 @@ <string name="kg_prompt_added_security_password" msgid="6053156069765029006">"اضافی سیکیورٹی کیلئے پاس ورڈ درکار ہے"</string> <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"آلہ منتظم کی جانب سے مقفل ہے"</string> <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"آلہ کو دستی طور پر مقفل کیا گیا تھا"</string> - <string name="kg_face_not_recognized" msgid="7903950626744419160">"تسلیم شدہ نہیں ہے"</string> + <string name="kg_face_not_recognized" msgid="7903950626744419160">"شناخت نہیں ہو سکی"</string> <string name="kg_face_sensor_privacy_enabled" msgid="939511161763558512">"فیس اَنلاک استعمال کرنے کیلئے، ترتیبات میں کیمرا تک رسائی کو آن کریں"</string> <string name="kg_password_default_pin_message" msgid="1434544655827987873">"{count,plural, =1{SIM کا PIN درج کریں۔ اس سے پہلے کہ آپ اپنا آلہ غیر مقفل کرنے کیلئے لازمی طور پر اپنے کیریئر سے رابطہ کریں آپ کے پاس # کوشش بچی ہے۔}other{SIM کا PIN درج کریں۔ آپ کے پاس # کوششیں بچی ہیں۔}}"</string> <string name="kg_password_default_puk_message" msgid="1025139786449741950">"{count,plural, =1{SIM اب غیر فعال ہے۔ جاری رکھنے کیلئے PUK کوڈ درج کریں۔ SIM کے مستقل طور پر ناقابل استعمال ہونے سے پہلے آپ کے پاس # کوشش بچی ہے۔ تفصیلات کیلئے کیریئر سے رابطہ کریں۔}other{SIM اب غیر فعال ہے۔ جاری رکھنے کیلئے PUK کوڈ درج کریں۔ SIM کے مستقل طور پر ناقابل استعمال ہونے سے پہلے آپ کے پاس # کوششیں بچی ہیں۔ تفصیلات کیلئے کیریئر سے رابطہ کریں۔}}"</string> diff --git a/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml b/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml index cf7a730dc379..a30a12221105 100644 --- a/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml +++ b/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml @@ -13,12 +13,16 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > - <!-- Since this layer list has just one layer, we can remove it and replace with the inner - layer drawable. However this should only be done when the flag +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="@color/qs_tile_ripple_color"> + <!-- We don't really use the ripple effect here, but changing it to LayerDrawable causes + performance regression, see: b/339412453. + Since this ripple has just one layer inside, we can try to remove that extra "background" + layer. However this should only be done when the flag com.android.systemui.qs_tile_focus_state has completed all its stages and this drawable fully replaces the previous one to ensure consistency with code sections searching for - specific ids in drawable hierarchy --> + specific ids in drawable hierarchy + --> <item android:id="@id/background"> <layer-list> @@ -48,4 +52,4 @@ </item> </layer-list> </item> -</layer-list>
\ No newline at end of file +</ripple>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml index 76d10ccb8a25..a598007833d7 100644 --- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml +++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml @@ -136,7 +136,8 @@ <TextView android:id="@+id/bluetooth_auto_on_toggle_title" android:layout_width="0dp" - android:layout_height="68dp" + android:layout_height="wrap_content" + android:minHeight="68dp" android:layout_marginBottom="20dp" android:maxLines="2" android:ellipsize="end" diff --git a/packages/SystemUI/res/layout/clipboard_overlay2.xml b/packages/SystemUI/res/layout/clipboard_overlay2.xml index 33ad2cd3a30f..521369e56652 100644 --- a/packages/SystemUI/res/layout/clipboard_overlay2.xml +++ b/packages/SystemUI/res/layout/clipboard_overlay2.xml @@ -31,7 +31,7 @@ android:layout_width="0dp" android:elevation="4dp" android:background="@drawable/shelf_action_chip_container_background" - android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal" + android:layout_marginStart="@dimen/overlay_action_container_minimum_edge_spacing" android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/actions_container" diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml index 76f7f3b2c759..2cb4b0233a90 100644 --- a/packages/SystemUI/res/layout/screenshot_shelf.xml +++ b/packages/SystemUI/res/layout/screenshot_shelf.xml @@ -33,8 +33,7 @@ android:layout_width="wrap_content" android:elevation="4dp" android:background="@drawable/shelf_action_chip_container_background" - android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal" - android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin" + android:layout_marginHorizontal="@dimen/overlay_action_container_minimum_edge_spacing" app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toTopOf="@id/guideline" > @@ -61,7 +60,7 @@ android:id="@+id/screenshot_preview_border" android:layout_width="0dp" android:layout_height="0dp" - android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal" + android:layout_marginStart="@dimen/overlay_action_container_minimum_edge_spacing" android:layout_marginTop="@dimen/overlay_border_width_neg" android:layout_marginEnd="@dimen/overlay_border_width_neg" android:layout_marginBottom="@dimen/screenshot_shelf_vertical_margin" @@ -153,7 +152,7 @@ android:id="@+id/screenshot_message_container" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginHorizontal="@dimen/overlay_action_container_margin_horizontal" + android:layout_marginHorizontal="@dimen/overlay_action_container_minimum_edge_spacing" android:layout_marginTop="4dp" android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom" android:paddingHorizontal="@dimen/overlay_action_container_padding_end" diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index ac523c17df25..cc8f9c2ec305 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Langdruk om legstukke te pasmaak"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Pasmaak legstukke"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App-ikoon vir gedeaktiveerde legstuk"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Wysig legstuk"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Verwyder"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Voeg legstuk by"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Pasmaak legstukke"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Legstukke op sluitskerm"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"kies legstuk"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"verwyder legstuk"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"plaas gekose legstuk"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Wissel gebruiker"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"aftrekkieslys"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle programme en data in hierdie sessie sal uitgevee word."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Voer uitsetinstellings in"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volumeglyers is uitgevou"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volumeglyers is ingevou"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"demp %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"ontdemp %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> speel tans op"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Oudio sal speel op"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Bel met"</string> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index 1236c976c57d..b00dc78d4de4 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ምግብሮችን ለማበጀት በረጅሙ ይጫኑ"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ምግብሮችን አብጅ"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"የመተግበሪያ አዶ ለተሰናከለ ምግብር"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"ምግብርን አርትዕ"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"አስወግድ"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ምግብር አክል"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ምግብሮችን አብጅ"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ምግብሮች በማያ ገጽ ቁልፍ ላይ"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ምግብር ይምረጡ"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ምግብር አስወግድ"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"በቦታ የተመረጠ ምግብር"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ተጠቃሚ ቀይር"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ወደታች ተጎታች ምናሌ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"በዚህ ክፍለ-ጊዜ ውስጥ ያሉ ሁሉም መተግበሪያዎች እና ውሂብ ይሰረዛሉ።"</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"የውጽዓት ቅንብሮችን ያስገቡ"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"የድምጽ ተንሸራታቾች ተዘርግቷል"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"የድምጽ ተንሸራታቾች ተሰብስቧል"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s ላይ ድምፀ-ከል አድርግ"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"የ%s ድምፀ-ከል አንሳ"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> እየተጫወተ ያለው በ"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"ኦዲዮ ይጫወታል በ"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"በጥሪ ላይ"</string> diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index 2eed2aa5b04e..d4981549654f 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -162,7 +162,7 @@ <string name="biometric_dialog_face_icon_description_confirmed" msgid="7918067993953940778">"تمّ التأكيد."</string> <string name="biometric_dialog_tap_confirm" msgid="9166350738859143358">"يمكنك النقر على \"تأكيد\" لإكمال المهمة."</string> <string name="biometric_dialog_tap_confirm_with_face" msgid="2378151312221818694">"تم فتح قفل جهازك عند تقريبه من وجهك."</string> - <string name="biometric_dialog_tap_confirm_with_face_alt_1" msgid="439152621640507113">"تم فتح قفل جهازك عند تقريبه من وجهك. اضغط للمتابعة."</string> + <string name="biometric_dialog_tap_confirm_with_face_alt_1" msgid="439152621640507113">"تم فتح الجهاز بالتعرّف على وجهك. اضغط للمتابعة."</string> <string name="biometric_dialog_tap_confirm_with_face_alt_2" msgid="8586608186457385108">"تم التعرّف على الوجه. اضغط للمتابعة."</string> <string name="biometric_dialog_tap_confirm_with_face_alt_3" msgid="2192670471930606539">"تم التعرّف على الوجه. للمتابعة، اضغط على رمز فتح القفل."</string> <string name="biometric_dialog_authenticated" msgid="7337147327545272484">"مصادقة"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"اضغط مع الاستمرار لتخصيص التطبيقات المصغّرة."</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"تخصيص التطبيقات المصغَّرة"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"رمز التطبيق المصغّر غير المفعّل"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"تعديل التطبيق المصغَّر"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"إزالة"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"إضافة تطبيق مصغّر"</string> @@ -629,8 +631,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"الدخول إلى إعدادات إخراج الصوت"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"تم توسيع أشرطة تمرير الصوت"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"تم تصغير أشرطة تمرير الصوت"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"كتم صوت \"%s\""</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"إعادة صوت \"%s\""</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"تشغيل <xliff:g id="LABEL">%s</xliff:g> على"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"سيتم تشغيل الصوت على"</string> <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index 16cd64b5d419..daefd834d85d 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ৱিজেট কাষ্টমাইজ কৰিবলৈ দীঘলীয়াকৈ টিপক"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ৱিজেট কাষ্টমাইজ কৰক"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"অক্ষম কৰা ৱিজেটৰ বাবে এপৰ চিহ্ন"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"ৱিজেট সম্পাদনা কৰক"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"আঁতৰাওক"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ৱিজেট যোগ দিয়ক"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ৱিজেট কাষ্টমাইজ কৰক"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"লক স্ক্ৰীনত ৱিজেট"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ৱিজেট বাছনি কৰক"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ৱিজেট আঁতৰাওক"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"বাছনি কৰা ৱিজেটটো ৰাখক"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ব্যৱহাৰকাৰী সলনি কৰক"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"পুল-ডাউনৰ মেনু"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই ছেশ্বনৰ আটাইবোৰ এপ্ আৰু ডেটা মচা হ\'ব।"</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"আউটপুট ছেটিং খোলক"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ভলিউমৰ শ্লাইডাৰ বিস্তাৰ কৰা আছে"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ভলিউমৰ শ্লাইডাৰ সংকোচন কৰা আছে"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s মিউট কৰক"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s আনমিউট কৰক"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"ইয়াত <xliff:g id="LABEL">%s</xliff:g> প্লে’ হৈ আছে"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"অডিঅ’ ইয়াত প্লে’ হ’ব"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"কল চলি আছে"</string> diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index 69cec1c6d115..399e621f4fc7 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Basıb saxlayaraq vidcetləri fərdiləşdirin"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Vidcetləri fərdiləşdirin"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Deaktiv edilmiş vidcet üçün tətbiq ikonası"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Vidceti redaktə edin"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Silin"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Vidcet əlavə edin"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Vidcetləri fərdiləşdirin"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Kilid ekranındakı vidcetlər"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"vidcet seçin"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"vidceti silin"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"seçilmiş vidceti yerləşdirin"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"aşağı çəkilən menyu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bu sessiyada bütün tətbiqlər və data silinəcək."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Çıxış ayarlarını daxil edin"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Səs slayderləri genişləndirildi"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Səs slayderləri yığcamlaşdırıldı"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s seçimini səssiz edin"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s seçimini səssiz rejimdən çıxarın"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> tətbiqində oxudulur"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio oxudulacaq"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Zəng edilir"</string> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index c2cc34afa758..1605666006c9 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Dugi pritisak za prilagođavanje vidžeta"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prilagodi vidžete"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacije za onemogućen vidžet"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Izmeni vidžet"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Ukloni"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodaj vidžet"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Prilagodite vidžete"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Vidžeti na zaključanom ekranu"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"izaberite vidžet"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"uklonite vidžet"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"postavite izabrani vidžet"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Zameni korisnika"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući meni"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i podaci u ovoj sesiji će biti izbrisani."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Unesite podešavanja izlaznog signala"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Klizači za jačinu zvuka su prošireni"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Klizači za jačinu zvuka su skupljeni"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"isključite zvuk za: %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"uključite zvuk za: %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> se pušta na"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Zvuk se pušta na"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Poziv na uređaju"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Tjuner za korisnički interfejs sistema"</string> <string name="status_bar" msgid="4357390266055077437">"Statusna traka"</string> <string name="demo_mode" msgid="263484519766901593">"Režim demonstracije za korisnički interfejs sistema"</string> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index f956c0fc20a0..89a4701a3ba3 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -97,8 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Левая граніца: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Правая граніца: <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Захавана ў праграму \"<xliff:g id="APP">%1$s</xliff:g>\" (у працоўным профілі)"</string> - <!-- no translation found for screenshot_private_profile_notification (1704440899154243171) --> - <skip /> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Захавана ў праграму \"<xliff:g id="APP">%1$s</xliff:g>\" (у прыватным профілі)"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Файлы"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"Праграма \"<xliff:g id="APPNAME">%1$s</xliff:g>\" выявіла гэты здымак экрана."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> і іншыя адкрытыя праграмы выявілі гэты здымак экрана."</string> @@ -448,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Доўга націскайце, каб наладзіць віджэты"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Наладзіць віджэты"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Значок праграмы для адключанага віджэта"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Змяніць віджэт"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Выдаліць"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Дадаць віджэт"</string> @@ -461,12 +462,9 @@ <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Закрыць віджэты на экране блакіроўкі"</string> <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Наладзіць віджэты"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Віджэты на экране блакіроўкі"</string> - <!-- no translation found for accessibility_action_label_select_widget (8897281501387398191) --> - <skip /> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"выбраць віджэт"</string> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"выдаліць віджэт"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"размясціць выбраны віджэт"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Перайсці да іншага карыстальніка"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"высоўнае меню"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Усе праграмы і даныя гэтага сеанса будуць выдалены."</string> @@ -631,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Перайсці да налад вываду"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Меню з паўзункамі гучнасці разгорнута"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Меню з паўзункамі гучнасці згорнута"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"выключыць гук (%s)"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"уключыць гук (%s)"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> прайграецца тут:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Аўдыявыхад:"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Ідзе выклік"</string> @@ -663,8 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Спадарожнікавая сувязь, дрэннае падключэнне"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Спадарожнікавая сувязь, добрае падключэнне"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Спадарожнікавая сувязь, падключэнне даступнае"</string> - <!-- no translation found for satellite_connected_carrier_text (118524195198532589) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Экстраннае спадарожнікавае падключэнне"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Працоўны профіль"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Цікава для некаторых, але не для ўсіх"</string> <string name="tuner_warning" msgid="1861736288458481650">"Наладка сістэмнага інтэрфейсу карыстальніка дае вам дадатковыя спосабы наладжвання і дапасоўвання карыстальніцкага інтэрфейсу Android. Гэтыя эксперыментальныя функцыі могуць змяніцца, перастаць працаваць або знікнуць у будучых версіях. Карыстайцеся з асцярожнасцю."</string> @@ -869,8 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Дадаць плітку"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Перамясціць на пазіцыю <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Дадаць на пазіцыю <xliff:g id="POSITION">%1$d</xliff:g>"</string> - <!-- no translation found for accessibilit_qs_edit_tile_add_move_invalid_position (2858467994472624487) --> - <skip /> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Няправільнае месца."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Пазіцыя <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Плітка дададзена"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Плітка выдалена"</string> diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml index c9d0cd220b82..bdd9fd45e0b9 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Натиснете продължително за персонализ. на приспос."</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Персонализиране на приспособленията"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Икона на приложение за деактивирано приспособление"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Редактиране на приспособлението"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Премахване"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Добавяне на приспособление"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Персонализиране на приспособленията"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Приспособления на заключения екран"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"избиране на приспособление"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"премахване на приспособлението"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"поставяне на избраното приспособление"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Превключване между потребителите"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"падащо меню"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Всички приложения и данни в тази сесия ще бъдат изтрити."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Отваряне на изходните настройки"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Плъзгачите за силата на звука са разгънати"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Плъзгачите за силата на звука са свити"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"спиране на звука на %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"включване на звука на %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Възпроизвеждане на <xliff:g id="LABEL">%s</xliff:g> на"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Аудиото ще се възпроизвежда на"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Активно обаждане"</string> diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index e2b4a6ddefe7..2c47b129b632 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"উইজেট কাস্টমাইজ করতে বেশিক্ষণ প্রেস করুন"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"উইজেট কাস্টমাইজ করুন"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"বন্ধ করা উইজেটের জন্য অ্যাপের আইকন"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"উইজেট এডিট করুন"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"সরান"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"উইজেট যোগ করুন"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"উইজেট কাস্টমাইজ করুন"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"লক স্ক্রিনে উইজেট"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"উইজেট বেছে নিন"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"উইজেট সরান"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"বেছে নেওয়া উইজেটটি রাখুন"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ব্যবহারকারী পাল্টে দিন"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"পুলডাউন মেনু"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"এই সেশনের সব অ্যাপ ও ডেটা মুছে ফেলা হবে।"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"আউটপুট সেটিংস লিখুন"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ভলিউম স্লাইডার বড় করা হয়েছে"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ভলিউম স্লাইডার আড়াল করা হয়েছে"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s মিউট করুন"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s আনমিউট করুন"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g>-এ প্লে করা হচ্ছে"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"অডিও এতে প্লে করা হবে"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"কল চালু আছে"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"সিস্টেম UI টিউনার"</string> <string name="status_bar" msgid="4357390266055077437">"স্ট্যাটাস বার"</string> <string name="demo_mode" msgid="263484519766901593">"সিস্টেম UI ডেমো মোড"</string> diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index 108bed8df6d1..8621b43bdafd 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -153,7 +153,7 @@ <string name="accessibility_send_smart_reply" msgid="8885032190442015141">"Pošalji"</string> <string name="cancel" msgid="1089011503403416730">"Otkaži"</string> <string name="biometric_dialog_logo" msgid="7681107853070774595">"Logotip aplikacije"</string> - <string name="biometric_dialog_confirm" msgid="2005978443007344895">"Potvrdite"</string> + <string name="biometric_dialog_confirm" msgid="2005978443007344895">"Potvrdi"</string> <string name="biometric_dialog_try_again" msgid="8575345628117768844">"Pokušaj ponovo"</string> <string name="biometric_dialog_empty_space_description" msgid="3330555462071453396">"Dodirnite da otkažete autentifikaciju"</string> <string name="biometric_dialog_face_icon_description_idle" msgid="4351777022315116816">"Pokušajte ponovo"</string> @@ -267,7 +267,7 @@ <string name="quick_settings_bluetooth_label" msgid="7018763367142041481">"Bluetooth"</string> <string name="quick_settings_bluetooth_detail_empty_text" msgid="5760239584390514322">"Nema dostupnih uparenih uređaja"</string> <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Dodirnite da povežete ili prekinete povezanost uređaja"</string> - <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Upari novi uređaj"</string> + <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Uparite novi uređaj"</string> <string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Prikaži sve"</string> <string name="turn_on_bluetooth" msgid="5681370462180289071">"Koristi Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Povezano"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pritisnite i zadržite da prilagodite vidžete"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prilagodite vidžete"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacije za onemogućeni vidžet"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Uredite vidžet"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Uklanjanje"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodajte vidžet"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Prilagođavanje vidžeta"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Vidžeti na zaključanom ekranu"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"odabir vidžeta"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"uklanjanje vidžeta"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"postavljanje odabranog vidžeta"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Zamijeni korisnika"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući meni"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Sve aplikacije i podaci iz ove sesije će se izbrisati."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Unos postavki izlaza"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Klizači jačine zvuka su prošireni"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Klizači jačine zvuka su suženi"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"isključivanje parametra %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"uključivanje parametra %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Reproducira se <xliff:g id="LABEL">%s</xliff:g> na"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Reprodukcija zvuka na"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Poziv putem"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Podešavač za korisnički interfejs sistema"</string> <string name="status_bar" msgid="4357390266055077437">"Statusna traka"</string> <string name="demo_mode" msgid="263484519766901593">"Demo način rada Sistemskog UI-ja"</string> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index 48fadbc5e8c7..6ebfd86b85ba 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantén premut per personalitzar els widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalitza els widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icona de l\'aplicació per a widget desactivat"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Edita el widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Suprimeix"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Afegeix un widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalitza els widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets a la pantalla de bloqueig"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"selecciona el widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"suprimeix el widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"col·loca el widget seleccionat"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Canvia d\'usuari"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú desplegable"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Totes les aplicacions i les dades d\'aquesta sessió se suprimiran."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Introdueix opcions de configuració de sortida"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Els controls lliscants de volum s\'han desplegat"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Els controls lliscants de volum s\'han replegat"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"silencia %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"deixa de silenciar %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"S\'està reproduint <xliff:g id="LABEL">%s</xliff:g> a"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Es reproduirà a"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Trucant des de"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Personalitzador d\'interfície d\'usuari"</string> <string name="status_bar" msgid="4357390266055077437">"Barra d\'estat"</string> <string name="demo_mode" msgid="263484519766901593">"Mode de demostració de la IU del sistema"</string> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index c90a0230aa31..7d2202da95c9 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -269,15 +269,15 @@ <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Klepnutím zařízení připojíte nebo odpojíte"</string> <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Spárovat nové zařízení"</string> <string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Zobrazit vše"</string> - <string name="turn_on_bluetooth" msgid="5681370462180289071">"Použít Bluetooth"</string> + <string name="turn_on_bluetooth" msgid="5681370462180289071">"Používat Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Připojeno"</string> <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Sdílení zvuku"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Uloženo"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"odpojit"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivovat"</string> <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Zítra znovu automaticky zapnout"</string> - <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Funkce jako Quick Share a Najdi moje zařízení využívají Bluetooth"</string> - <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth se zapne zítra ráno"</string> + <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Bluetooth využívají funkce jako Quick Share a Najdi moje zařízení."</string> + <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth se zapne zítra ráno."</string> <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"Sdílení zvuku"</string> <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="8626191139359072540">"Sdílení zvuku"</string> <string name="quick_settings_bluetooth_secondary_label_battery_level" msgid="4182034939479344093">"Baterie: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%s</xliff:g>"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Dlouhým stisknutím můžete přizpůsobit widgety"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Přizpůsobit widgety"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikace s deaktivovaným widgetem"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Upravit widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Odstranit"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Přidat widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Přizpůsobit widgety"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgety na obrazovce uzamčení"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"vybrat widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"odstranit widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"umístit vybraný widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Přepnout uživatele"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rozbalovací nabídka"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Veškeré aplikace a data v této relaci budou vymazána."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Otevřít nastavení výstupu"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Posuvníky hlasitosti jsou rozbalené"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Posuvníky hlasitosti jsou sbalené"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"ztlumíte %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"zapnete zvuk %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Přehrávání <xliff:g id="LABEL">%s</xliff:g> přes"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Zvuk se bude přehrávat přes"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Volání na zařízení"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Nástroj na ladění uživatelského rozhraní systému"</string> <string name="status_bar" msgid="4357390266055077437">"Stavový řádek"</string> <string name="demo_mode" msgid="263484519766901593">"Ukázkový režim uživatelského rozhraní systému"</string> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index 036f855b7a8f..ec03938ad7e2 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Hold fingeren nede for at tilpasse widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Tilpas widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Appikon for deaktiveret widget"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Rediger widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Fjern"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Tilføj widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Tilpas widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets på låseskærmen"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"vælg widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"fjern widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"placer valgt widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Skift bruger"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullemenu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apps og data i denne session slettes."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Angiv indstillinger for output"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Lydstyrkeskydere er udvidet"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Lydstyrkeskydere er skjult"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"slå lyden fra for %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"slå lyden til for %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Afspiller <xliff:g id="LABEL">%s</xliff:g> på"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Lyden afspilles på"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Ringer på"</string> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index 42efdc22f1db..d38ad3ae6a83 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Lange drücken, um Widgets anzupassen"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Widgets anpassen"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App-Symbol für deaktiviertes Widget"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Widget bearbeiten"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Entfernen"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Widget hinzufügen"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Widgets anpassen"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets auf dem Sperrbildschirm"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"Widget auswählen"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"Widget entfernen"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ausgewähltes Widget in den Bearbeitungsmodus versetzen"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Nutzer wechseln"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"Pull-down-Menü"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle Apps und Daten in dieser Sitzung werden gelöscht."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Ausgabeeinstellungen angeben"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Lautstärkeregler maximiert"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Lautstärkeregler minimiert"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s stummzuschalten"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"Stummschaltung von %s aufzuheben"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Wiedergabe von <xliff:g id="LABEL">%s</xliff:g> über"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audiowiedergabe über"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Anruf auf"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Statusleiste"</string> <string name="demo_mode" msgid="263484519766901593">"Demomodus der System-UI"</string> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index 72ab8848c6ad..c6dad44b10e9 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Παρατεταμένο πάτημα για προσαρμογή γραφ. στοιχείων"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Προσαρμογή γραφικών στοιχείων"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Εικονίδιο εφαρμογής για απενεργοποιημένο γραφικό στοιχείο"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Επεξεργασία γραφικού στοιχείου"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Κατάργηση"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Προσθήκη γραφικού στοιχείου"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Προσαρμογή γραφικών στοιχείων"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Γραφικά στοιχεία στην οθόνη κλειδώματος"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"επιλογή γραφικού στοιχείου"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"κατάργηση γραφικού στοιχείου"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"τοποθέτηση επιλεγμένου γραφικού στοιχείου"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Εναλλαγή χρήστη"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"αναπτυσσόμενο μενού"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Όλες οι εφαρμογές και τα δεδομένα αυτής της περιόδου σύνδεσης θα διαγραφούν."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Εισαγωγή ρυθμίσεων εξόδου"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Τα ρυθμιστικά έντασης ήχου αναπτύχθηκαν"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Τα ρυθμιστικά έντασης ήχου συμπτύχθηκαν"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"σίγαση %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"κατάργηση σίγασης %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Αναπαραγωγή <xliff:g id="LABEL">%s</xliff:g> σε"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Ο ήχος θα παίξει σε"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Ενεργή κλήση σε"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Γραμμή κατάστασης"</string> <string name="demo_mode" msgid="263484519766901593">"Λειτουργία επίδειξης διεπαφής χρήστη συστήματος"</string> diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index e414cd4e857a..cbb22984c351 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customise widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Customise widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App icon for disabled widget"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Customise widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets on lock screen"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"select widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"remove widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"place selected widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Enter output settings"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volume sliders expanded"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volume sliders collapsed"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"mute %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"unmute %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Playing <xliff:g id="LABEL">%s</xliff:g> on"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio will play on"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Calling on"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Status bar"</string> <string name="demo_mode" msgid="263484519766901593">"System UI demo mode"</string> diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index 204f21d0c23a..fa007b67faee 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -447,6 +447,7 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customize widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Customize widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App icon for disabled widget"</string> + <string name="icon_description_for_pending_widget" msgid="8413816401868001755">"App icon for a widget being installed"</string> <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string> @@ -627,8 +628,10 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Enter output settings"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volume sliders expanded"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volume sliders collapsed"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"mute %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"unmute %s"</string> + <string name="volume_panel_hint_mute" msgid="2153922288568199077">"Mute %s"</string> + <string name="volume_panel_hint_unmute" msgid="4831850937582282340">"Unmute %s"</string> + <string name="volume_panel_hint_muted" msgid="1124844870181285320">"muted"</string> + <string name="volume_panel_hint_vibrate" msgid="4136223145435914132">"vibrate"</string> <string name="media_output_label_title" msgid="872824698593182505">"Playing <xliff:g id="LABEL">%s</xliff:g> on"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio will play on"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Calling on"</string> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index e414cd4e857a..cbb22984c351 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customise widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Customise widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App icon for disabled widget"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Customise widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets on lock screen"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"select widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"remove widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"place selected widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Enter output settings"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volume sliders expanded"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volume sliders collapsed"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"mute %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"unmute %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Playing <xliff:g id="LABEL">%s</xliff:g> on"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio will play on"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Calling on"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Status bar"</string> <string name="demo_mode" msgid="263484519766901593">"System UI demo mode"</string> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index e414cd4e857a..cbb22984c351 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customise widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Customise widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App icon for disabled widget"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Customise widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets on lock screen"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"select widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"remove widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"place selected widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Switch user"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"All apps and data in this session will be deleted."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Enter output settings"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volume sliders expanded"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volume sliders collapsed"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"mute %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"unmute %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Playing <xliff:g id="LABEL">%s</xliff:g> on"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio will play on"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Calling on"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Status bar"</string> <string name="demo_mode" msgid="263484519766901593">"System UI demo mode"</string> diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index ee28a624b462..248360bac608 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -447,6 +447,7 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Long press to customize widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Customize widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App icon for disabled widget"</string> + <string name="icon_description_for_pending_widget" msgid="8413816401868001755">"App icon for a widget being installed"</string> <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Remove"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Add widget"</string> @@ -627,8 +628,10 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Enter output settings"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volume sliders expanded"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volume sliders collapsed"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"mute %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"unmute %s"</string> + <string name="volume_panel_hint_mute" msgid="2153922288568199077">"Mute %s"</string> + <string name="volume_panel_hint_unmute" msgid="4831850937582282340">"Unmute %s"</string> + <string name="volume_panel_hint_muted" msgid="1124844870181285320">"muted"</string> + <string name="volume_panel_hint_vibrate" msgid="4136223145435914132">"vibrate"</string> <string name="media_output_label_title" msgid="872824698593182505">"Playing <xliff:g id="LABEL">%s</xliff:g> on"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio will play on"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Calling on"</string> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 42424efdbeb6..28230f1d573d 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantén presionado para personalizar los widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ícono de la app de widget inhabilitado"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Modificar widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Quitar"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Agregar widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizar widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets en la pantalla de bloqueo"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"Seleccionar widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"quitar widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"colocar widget seleccionado"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar usuario"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú expandible"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Se eliminarán las aplicaciones y los datos de esta sesión."</string> @@ -619,7 +619,7 @@ <string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"Audio espacial"</string> <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Desactivar"</string> <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Fijar"</string> - <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Seg. de cabeza"</string> + <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Monitoreo de cabeza"</string> <string name="volume_ringer_change" msgid="3574969197796055532">"Presiona para cambiar el modo de timbre"</string> <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"silenciar"</string> <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"dejar de silenciar"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Ingresar configuración de salida"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Controles deslizantes del volumen expandidos"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Controles deslizantes del volumen colapsados"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"silenciar %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"activar sonido %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Reproduciendo <xliff:g id="LABEL">%s</xliff:g> en"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Se reproducirá en"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Llamando en"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Sintonizador de IU del sistema"</string> <string name="status_bar" msgid="4357390266055077437">"Barra de estado"</string> <string name="demo_mode" msgid="263484519766901593">"Modo demostración de la IU del sistema"</string> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 77989e4ac7d3..fd2ac8582ae4 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantén pulsado para personalizar los widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icono de la aplicación de widget inhabilitado"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Quitar"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Añadir widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizar widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets en la pantalla de bloqueo"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"seleccionar widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"eliminar widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"colocar widget seleccionado"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar de usuario"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú desplegable"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Se eliminarán todas las aplicaciones y datos de esta sesión."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Introducir los ajustes de salida"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Controles deslizantes de volumen desplegados"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Controles deslizantes de volumen contraídos"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"silenciar %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"dejar de silenciar %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Reproduciendo <xliff:g id="LABEL">%s</xliff:g> en"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Se reproducirá en"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Llamando desde"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Configurador de UI del sistema"</string> <string name="status_bar" msgid="4357390266055077437">"Barra de estado"</string> <string name="demo_mode" msgid="263484519766901593">"Modo Demo de UI del sistema"</string> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index a3663a0bd958..7f97176012d7 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Vajutage pikalt vidinate kohandamiseks"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Kohanda vidinaid"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Keelatud vidina rakenduseikoon"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Muuda vidinat"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Eemalda"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Lisa vidin"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Kohanda vidinaid"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Lukustuskuva vidinad"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"vidina valimine"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"eemaldage vidin"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"asetage valitud vidin"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Kasutaja vahetamine"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rippmenüü"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Seansi kõik rakendused ja andmed kustutatakse."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Sisestage väljundseaded"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Helitugevuse liugurid laiendatud"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Helitugevuse liugurid ahendatud"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"vaigistab %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"tühistab %s vaigistuse"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Esitamine jätkub seadmes <xliff:g id="LABEL">%s</xliff:g>"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Heli esitatakse seadmes"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Helistamine seadmes"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Süsteemi kasutajaliidese tuuner"</string> <string name="status_bar" msgid="4357390266055077437">"Olekuriba"</string> <string name="demo_mode" msgid="263484519766901593">"Süsteemi kasutajaliidese demorežiim"</string> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index 23de72ea0527..685b5921bcd0 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Widgetak pertsonalizatzeko, sakatu luze"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Pertsonalizatu widgetak"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Desgaitutako widgetaren aplikazio-ikonoa"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Editatu widgeta"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Kendu"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Gehitu widget bat"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Pertsonalizatu widgetak"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Pantaila blokeatuko widgetak"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"hautatu widget bat"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"kendu widgeta"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"kokatu hautatutako widgeta"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Aldatu erabiltzailea"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"zabaldu menua"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Saioko aplikazio eta datu guztiak ezabatuko dira."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Ireki emaitzaren ezarpenak"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Bolumenaren botoi lerrakorrak zabalduta daude"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Bolumenaren botoi lerrakorrak tolestuta daude"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"desaktibatu honen audioa: %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"aktibatu honen audioa: %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> hemen erreproduzitzen:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audioak abian jarraituko du hemen:"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Honen bidez deitzen"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Sistemaren erabiltzaile-interfazearen konfiguratzailea"</string> <string name="status_bar" msgid="4357390266055077437">"Egoera-barra"</string> <string name="demo_mode" msgid="263484519766901593">"Sistemaren erabiltzaile-interfazearen demo modua"</string> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 9feeaeeec31a..d21fdd330c11 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"برای سفارشیسازی ابزارکها، فشار طولانی دهید"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"سفارشیسازی ابزارکها"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"نماد برنامه برای ابزارک غیرفعال"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"ویرایش ابزارک"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"برداشتن"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"افزودن ابزارک"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"سفارشیسازی ابزارکها"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ابزارکها در صفحه قفل"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"انتخاب ابزارک"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"برداشتن ابزارک"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"جایگذاری ابزارک انتخابشده"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"تغییر کاربر"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"منوی پایینپر"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"همه برنامهها و دادههای این جلسه حذف خواهد شد."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"تنظیمات خروجی را وارد کنید"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"لغزندههای صدا ازهم باز شدند"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"لغزندههای صدا جمع شدند"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"بیصدا کردن %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"باصدا کردن %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"درحال پخش <xliff:g id="LABEL">%s</xliff:g> در"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"صدا در این دستگاه پخش میشود:"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"تماس برقرار است"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"تنظیمکننده واسط کاربری سیستم"</string> <string name="status_bar" msgid="4357390266055077437">"نوار وضعیت"</string> <string name="demo_mode" msgid="263484519766901593">"حالت نمایشی واسط کاربری سیستم"</string> diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index 68319fd8b4b6..8910bf320593 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Yksilöi widgetit pitkällä painalluksella"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Muokkaa widgettejä"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Käytöstä poistetun widgetin sovelluskuvake"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Muokkaa widgetiä"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Poista"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Lisää widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Muokkaa widgetejä"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgetit lukitusnäytöllä"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"valitse widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"poista widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"aseta valittu widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Vaihda käyttäjää"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"alasvetovalikko"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Kaikki sovellukset ja tämän istunnon tiedot poistetaan."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Lisää tuloasetukset"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Äänenvoimakkuuden liukusäätimet laajennettu"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Äänenvoimakkuuden liukusäätimet tiivistetty"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"mykistä: %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"poista mykistys: %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Toistetaan: <xliff:g id="LABEL">%s</xliff:g>"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audiota toistetaan laitteella"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Puhelu kesken:"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Tilapalkki"</string> <string name="demo_mode" msgid="263484519766901593">"Käyttöliittymän esittelytila"</string> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index fd6ec7a371b8..df5f59f80dd1 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Maintenez le doigt pour personnaliser les widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personnaliser les widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icône d\'application pour un widget désactivé"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Modifier le widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Retirer"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ajouter un widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personnaliser les widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets sur l\'écran de verrouillage"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"sélectionner le widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"retirer le widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"placer le widget sélectionné"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Changer d\'utilisateur"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu déroulant"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applications et les données de cette session seront supprimées."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Entrer les paramètres de sortie"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Curseurs de volume développés"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Curseurs de volume réduits"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"Désactivez le son de %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"Réactivez le son de %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Lecture de <xliff:g id="LABEL">%s</xliff:g> sur"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Lecture audio sur"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Appel en cours"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Barre d\'état"</string> <string name="demo_mode" msgid="263484519766901593">"Mode Démo de l\'interface système"</string> @@ -1012,10 +1017,10 @@ <string name="accessibility_floating_button_hidden_notification_text" msgid="1457021647040915658">"Touchez pour afficher le bouton d\'accessibilité"</string> <string name="accessibility_floating_button_undo_message_label_text" msgid="9017658016426242640">"Le raccourci <xliff:g id="FEATURE_NAME">%s</xliff:g> a été retiré"</string> <string name="accessibility_floating_button_undo_message_number_text" msgid="4909270290725226075">"{count,plural, =1{# raccourci retiré}one{# raccourci retiré}many{# de raccourcis retirés}other{# raccourcis retirés}}"</string> - <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Déplacer dans coin sup. gauche"</string> - <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Déplacer dans coin sup. droit"</string> - <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Déplacer dans coin inf. gauche"</string> - <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Déplacer dans coin inf. droit"</string> + <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Déplacer en haut à gauche"</string> + <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Déplacer en haut à droite"</string> + <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Déplacer en bas à gauche"</string> + <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Déplacer en bas à droite"</string> <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Rapprocher du bord et masquer"</string> <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Éloigner du bord et afficher"</string> <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"Retirer"</string> diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml index 47f469ff4605..b02d547e08a8 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Appuyez de manière prolongée pour personnaliser les widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personnaliser les widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icône d\'appli du widget désactivé"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Modifier le widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Supprimer"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ajouter un widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personnaliser les widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets sur l\'écran de verrouillage"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"sélectionner un widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"supprimer le widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"positionner le widget sélectionné"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Changer d\'utilisateur"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu déroulant"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toutes les applications et les données de cette session seront supprimées."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Accéder aux paramètres de sortie"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Curseurs de volume développés"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Curseurs de volume réduits"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"couper le son de %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"réactiver le son de %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Diffusion de <xliff:g id="LABEL">%s</xliff:g> sur"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Lecture audio sur"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Appel défini sur"</string> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index e7037b39fe33..8e2a0115b448 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pulsación longa para personalizar os widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icona da aplicación de widget desactivado"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Quitar"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Engadir widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizar os widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets na pantalla de bloqueo"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"seleccionar widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"quitar o widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"colocar o widget seleccionado"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Cambiar usuario"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menú despregable"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Eliminaranse todas as aplicacións e datos desta sesión."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Introducir a configuración de saída"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Controis desprazables de volume despregados"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Controis desprazables de volume contraídos"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"silenciar %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"activar o son de %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Reproducindo <xliff:g id="LABEL">%s</xliff:g> en"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Reproducirase en"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Chamada en curso"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Configurador da IU do sistema"</string> <string name="status_bar" msgid="4357390266055077437">"Barra de estado"</string> <string name="demo_mode" msgid="263484519766901593">"Modo de demostración da IU do sistema"</string> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index b3c8e1eb5a88..c0f6be96115c 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"વિજેટ કસ્ટમાઇઝ કરવા માટે થોડીવાર દબાવી રાખો"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"વિજેટ કસ્ટમાઇઝ કરો"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"બંધ કરેલા વિજેટ માટેની ઍપનું આઇકન"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"વિજેટમાં ફેરફાર કરો"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"કાઢી નાખો"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"વિજેટ ઉમેરો"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"વિજેટ કસ્ટમાઇઝ કરો"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"લૉક સ્ક્રીન પર વિજેટ"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"વિજેટ પસંદ કરો"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"વિજેટ કાઢી નાખો"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"પસંદ કરેલું વિજેટ મૂકો"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"વપરાશકર્તા સ્વિચ કરો"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"પુલડાઉન મેનૂ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"આ સત્રમાંની તમામ ઍપ અને ડેટા કાઢી નાખવામાં આવશે."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"આઉટપુટના સેટિંગ દાખલ કરો"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"વૉલ્યૂમના સ્લાઇડર મોટા કરવામાં આવ્યા"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"વૉલ્યૂમના સ્લાઇડર નાના કરવામાં આવ્યા"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%sને મ્યૂટ કરો"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%sને અનમ્યૂટ કરો"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> વગાડી રહ્યાં છીએ"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"ઑડિયો આની પર વાગશે"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"કૉલ ચાલુ છે"</string> diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 28825845bb08..4cf74b5f0a08 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"विजेट पसंद के मुताबिक बनाने के लिए उसे दबाकर रखें"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"विजेट अपनी पसंद के मुताबिक बनाएं"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"बंद किए गए विजेट के लिए ऐप्लिकेशन आइकॉन"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"विजेट में बदलाव करें"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"हटाएं"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"विजेट जोड़ें"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"विजेट अपनी पसंद के मुताबिक बनाएं"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"लॉक स्क्रीन पर विजेट"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"विजेट चुनें"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"विजेट हटाएं"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"चुने गए विजेट के लिए जगह चुनें"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"उपयोगकर्ता बदलें"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेन्यू"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"इस सेशन के सभी ऐप्लिकेशन और डेटा को हटा दिया जाएगा."</string> @@ -562,7 +562,7 @@ <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"इस डिवाइस का प्रबंधन आपके अभिभावक करते हैं. अभिभावक आपके डिवाइस से जुड़ी जानकारी देख सकते हैं. साथ ही, इसे प्रबंधित कर सकते हैं. इनमें आपके इस्तेमाल किए गए ऐप्लिकेशन, जगह की जानकारी, और डिवाइस के इस्तेमाल में बिताए गए समय जैसी जानकारी शामिल है."</string> <string name="legacy_vpn_name" msgid="4174223520162559145">"वीपीएन"</string> <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"TrustAgent की वजह से अनलॉक रखा गया है"</string> - <string name="kg_prompt_after_adaptive_auth_lock" msgid="2587481497846342760">"कई बार पुष्टि करने की कोशिश की वजह से, डिवाइस लॉक है"</string> + <string name="kg_prompt_after_adaptive_auth_lock" msgid="2587481497846342760">"कई बार पुष्टि करने की कोशिशों की वजह से, डिवाइस लॉक हो गया है"</string> <string name="keyguard_indication_after_adaptive_auth_lock" msgid="2323400645470712787">"डिवाइस लॉक हो गया है\nपुष्टि नहीं की जा सकी"</string> <string name="zen_mode_and_condition" msgid="5043165189511223718">"<xliff:g id="ZEN_MODE">%1$s</xliff:g>. <xliff:g id="EXIT_CONDITION">%2$s</xliff:g>"</string> <string name="accessibility_volume_settings" msgid="1458961116951564784">"साउंड सेटिंग"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"आउटपुट की सेटिंग डालें"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"आवाज़ के स्लाइडर को बड़ा किया गया"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"आवाज़ के स्लाइडर को छोटा किया गया"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s को म्यूट करें"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s को अनम्यूट करें"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> इस पर चल रहा है"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"ऑडियो इस पर चलेगा"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"कॉल चालू है"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"सिस्टम यूज़र इंटरफ़ेस (यूआई) ट्यूनर"</string> <string name="status_bar" msgid="4357390266055077437">"स्टेटस बार"</string> <string name="demo_mode" msgid="263484519766901593">"सिस्टम यूज़र इंटरफ़ेस (यूआई) डेमो मोड"</string> diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index 560938bb882b..b211c9437539 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -369,8 +369,8 @@ <string name="quick_settings_contrast_medium" msgid="5158352575583902566">"Srednji"</string> <string name="quick_settings_contrast_high" msgid="656049259587494499">"Visoki"</string> <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"Slušna pomagala"</string> - <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"Slušni uređaji"</string> - <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Uparivanje novog uređaja"</string> + <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"Slušna pomagala"</string> + <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Uparite novi uređaj"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Kliknite da biste uparili novi uređaj"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Ažuriranje unaprijed definiranih postavki nije uspjelo"</string> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Želite li deblokirati mikrofon uređaja?"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Dugo pritisnite za prilagodbu widgeta"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prilagodi widgete"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacije za onemogućeni widget"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Uredi widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Ukloni"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodaj widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Prilagodi widgete"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgeti na zaključanom zaslonu"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"odaberi widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ukloni widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"postavi odabrani widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Promjena korisnika"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"padajući izbornik"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Izbrisat će se sve aplikacije i podaci u ovoj sesiji."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Unesite postavke izlaza"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Proširivanje klizača za glasnoću"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Sažimanje klizača za glasnoću"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"isključili zvuk za sljedeće: %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"uključili zvuk za sljedeće: %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> se reproducira na"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Zvuk će se reproducirati na"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Pozivanje na uređaju"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Ugađanje korisničkog sučelja sustava"</string> <string name="status_bar" msgid="4357390266055077437">"Traka statusa"</string> <string name="demo_mode" msgid="263484519766901593">"Demo način korisničkog sučelja sustava"</string> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index 23166431bb28..e736537cc125 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Nyomja meg hosszan a modulok személyre szabásához"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Modulok személyre szabása"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Letiltott modul alkalmazásikonja"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Modul szerkesztése"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Eltávolítás"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Modul hozzáadása"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Modulok személyre szabása"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Modulok a lezárási képernyőn"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"modul kiválasztása"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"modul törlése"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"kijelölt modul áthelyezése"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Felhasználóváltás"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"lehúzható menü"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"A munkamenetben található összes alkalmazás és adat törlődni fog."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Kimenet beállításainak megadása"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Hangerő-szabályozók kibontva"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Hangerő-szabályozók összecsukva"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s némítása"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s némításának feloldása"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> lejátszása itt:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Hang lejátszása itt:"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Hívás folyamatban itt:"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Kezelőfelület-hangoló"</string> <string name="status_bar" msgid="4357390266055077437">"Állapotsor"</string> <string name="demo_mode" msgid="263484519766901593">"A rendszer kezelőfelületének demómódja"</string> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index 3d70b70556ab..6a905c598e00 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Երկար սեղմեք՝ վիջեթները հարմարեցնելու համար"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Հարմարեցնել վիջեթները"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Հավելվածի պատկերակ անջատված վիջեթի համար"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Փոփոխել վիջեթը"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Հեռացնել"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ավելացնել վիջեթ"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Հարմարեցնել վիջեթները"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Վիջեթներ կողպէկրանին"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ընտրել վիջեթ"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"հեռացնել վիջեթը"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"տեղադրել ընտրված վիջեթը"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Անջատել օգտվողին"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"իջնող ընտրացանկ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Այս աշխատաշրջանի բոլոր հավելվածներն ու տվյալները կջնջվեն:"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Բացել նվագարկման կարգավորումները"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Ձայնի ուժգնության սահիչները ծավալված են"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Ձայնի ուժգնության սահիչները ծալված են"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"անջատել ձայնը (%s)"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"միացնել ձայնը (%s)"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g>. նվագարկվում է"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Աուդիոն կնվագարկի"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Զանգն ընթացքում է"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Համակարգի ՕՄ-ի կարգավորիչ"</string> <string name="status_bar" msgid="4357390266055077437">"Կարգավիճակի գոտի"</string> <string name="demo_mode" msgid="263484519766901593">"Համակարգի միջերեսի ցուցադրական ռեժիմ"</string> diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index bc7bb0fcd6a4..23d83974493c 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Tekan lama untuk menyesuaikan widget"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Sesuaikan widget"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikon aplikasi untuk widget yang dinonaktifkan"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Hapus"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Tambahkan widget"</string> @@ -629,12 +631,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Masukkan setelan perangkat output"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Penggeser volume diluaskan"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Penggeser volume diciutkan"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"membisukan %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"membunyikan %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Memutar <xliff:g id="LABEL">%s</xliff:g> di"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio akan diputar di"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Menelepon di"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Penyetel Antarmuka Pengguna Sistem"</string> <string name="status_bar" msgid="4357390266055077437">"Bilah status"</string> <string name="demo_mode" msgid="263484519766901593">"Mode demo UI sistem"</string> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index b30c89845138..2c77eb1f0345 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Haltu inni til að sérsníða græjur"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Sérsníða græjur"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Forritstákn fyrir græju sem slökkt er á"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Breyta græju"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Fjarlægja"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Bæta græju við"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Sérsníða græjur"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Græjur á lásskjá"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"velja græju"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"fjarlægja græju"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"koma valinni græju fyrir"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Skipta um notanda"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"Fellivalmynd"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Öllum forritum og gögnum í þessari lotu verður eytt."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Færa inn stillingar úttaks"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Stækkaðir hljóðstyrkssleðar"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Minnkaðir hljóðstyrkssleðar"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"þagga %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"kveikja á hljóði %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Í spilun í <xliff:g id="LABEL">%s</xliff:g>"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Hljóð heldur áfram að spilast"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Símtal í gangi"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Fínstillingar kerfisviðmóts"</string> <string name="status_bar" msgid="4357390266055077437">"Stöðustika"</string> <string name="demo_mode" msgid="263484519766901593">"Prufustilling kerfisviðmóts"</string> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index 210209958ae9..056c2867388a 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Premi a lungo per personalizzare i widget"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizza widget"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icona dell\'app per widget disattivati"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Modifica widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Rimuovi"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Aggiungi widget"</string> @@ -629,12 +631,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Inserisci impostazioni di uscita"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Cursori volume espansi"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Cursori volume compressi"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"disattivare audio di %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"riattivare audio di %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> in riproduzione su"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio riprodotto su:"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Chiamata in corso"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Ottimizzatore UI di sistema"</string> <string name="status_bar" msgid="4357390266055077437">"Barra di stato"</string> <string name="demo_mode" msgid="263484519766901593">"Modalità demo dell\'interfaccia utente di sistema"</string> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index 5be4172db9d1..6670b097ac53 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -269,7 +269,7 @@ <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"אפשר להקיש כדי להתחבר למכשיר או להתנתק ממנו"</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="turn_on_bluetooth" msgid="5681370462180289071">"שימוש ב-Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"מחובר"</string> <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"שיתוף אודיו"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"נשמר"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"לוחצים לחיצה ארוכה כדי להתאים אישית את הווידג\'טים"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"התאמה אישית של ווידג\'טים"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"סמל האפליקציה לווידג\'ט שהושבת"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"עריכת הווידג\'ט"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"הסרה"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"הוספת ווידג\'ט"</string> @@ -629,12 +631,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"הזנה של הגדרות הפלט"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"פסי ההזזה של עוצמת הקול במצב מורחב"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"פסי ההזזה של עוצמת הקול במצב מכווץ"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"השתקה של %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"ביטול ההשתקה של %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"הפעלה של <xliff:g id="LABEL">%s</xliff:g> במכשיר"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"האודיו יופעל במכשיר"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"מתבצעת שיחה במכשיר"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"שורת סטטוס"</string> <string name="demo_mode" msgid="263484519766901593">"מצב הדגמה בממשק המשתמש של המערכת"</string> diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 62a3a4324658..e3c0dbcf9992 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -272,7 +272,7 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"Bluetooth を使用"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"接続しました"</string> <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"音声の共有"</string> - <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"保存しました"</string> + <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"保存済み"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"接続を解除"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"有効化"</string> <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"明日自動的に ON に戻す"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"長押ししてウィジェットをカスタマイズ"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ウィジェットのカスタマイズ"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"無効なウィジェットのアプリアイコン"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"ウィジェットを編集"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"削除"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ウィジェットを追加"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ウィジェットのカスタマイズ"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ロック画面のウィジェット"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ウィジェットを選択"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ウィジェットを削除"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"選択したウィジェットを配置"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ユーザーを切り替える"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"プルダウン メニュー"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"このセッションでのアプリとデータはすべて削除されます。"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"出力の設定を入力してください"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"音量スライダーを開きました"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"音量スライダーを閉じました"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s をミュート"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s のミュートを解除"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> の再生先:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"音声の再生先"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"通話中"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"システムUI調整ツール"</string> <string name="status_bar" msgid="4357390266055077437">"ステータスバー"</string> <string name="demo_mode" msgid="263484519766901593">"システム UI デモモード"</string> diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index 2a4f5d68c1f7..0251ac0e9d7c 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ხანგრძლივად დააჭირეთ ვიჯეტების მოსარგებად"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ვიჯეტების მორგება"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"აპის ხატულა გათიშული ვიჯეტისთვის"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"ვიჯეტის რედაქტირება"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"ამოშლა"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ვიჯეტის დამატება"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ვიჯეტების მორგება"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ვიჯეტები ჩაკეტილ ეკრანზე"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ვიჯეტის არჩევა"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ვიჯეტის ამოშლა"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"არჩეული ვიჯეტის განთავსება"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"მომხმარებლის გადართვა"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ჩამოშლადი მენიუ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ამ სესიის ყველა აპი და მონაცემი წაიშლება."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"აუდიოს გამოსვლის პარამეტრების გახსნა"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ხმის სლაიდერების გაფართოება"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ხმის სლაიდერების ჩაკეცვა"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s-ის დადუმება"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s-ის დადუმების მოხსნა"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"უკრავს <xliff:g id="LABEL">%s</xliff:g>:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"აუდიო დაიკვრება"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"მიმდინარეობს ზარი"</string> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index 398677ee6396..9c96a4eb6b60 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Виджеттерді бейімдеу үшін ұзақ басып тұрыңыз."</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Виджеттерді реттеу"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Өшірілген виджеттің қолданба белгішесі"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Виджетті өзгерту"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Өшіру"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Виджет қосу"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Виджеттерді бейімдеу"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Құлыптаулы экрандағы виджеттер"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"виджет таңдау"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"виджетті өшіру"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"таңдалған виджетті орналастыру"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Пайдаланушыны ауыстыру"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ашылмалы мәзір"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Осы сеанстағы барлық қолданба мен дерек жойылады."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Шығыс параметрлерін енгізу"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Дыбыс деңгейінің жүгірткі реттегіштері жайылды."</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Дыбыс деңгейінің жүгірткі реттегіштері жиылды."</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s дыбысын өшіру"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s дыбысын қосу"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> ойнатылатын құрылғы:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Аудио ойнатылатын құрылғы:"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Қоңырау шалып жатыр"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Жүйелік пайдаланушылық интерфейс тюнері"</string> <string name="status_bar" msgid="4357390266055077437">"Күйін көрсету жолағы"</string> <string name="demo_mode" msgid="263484519766901593">"Жүйе интерфейсінің демо режимі"</string> diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index e32da00ef6a9..dc9a747a70b4 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ចុចឱ្យយូរ ដើម្បីប្ដូរធាតុក្រាហ្វិកតាមបំណង"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ប្ដូរធាតុក្រាហ្វិកតាមបំណង"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"រូបកម្មវិធីសម្រាប់ធាតុក្រាហ្វិកដែលបានបិទ"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"កែធាតុក្រាហ្វិក"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"ដកចេញ"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"បញ្ចូលធាតុក្រាហ្វិក"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ប្ដូរធាតុក្រាហ្វិកតាមបំណង"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ធាតុក្រាហ្វិកនៅលើអេក្រង់ចាក់សោ"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ជ្រើសរើសធាតុក្រាហ្វិក"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ដកធាតុក្រាហ្វិកចេញ"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ដាក់ធាតុក្រាហ្វិកដែលបានជ្រើសរើស"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ប្ដូរអ្នកប្រើ"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ម៉ឺនុយទាញចុះ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"កម្មវិធី និងទិន្នន័យទាំងអស់ក្នុងវគ្គនេះនឹងត្រូវលុប។"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"ចូលការកំណត់ឧបករណ៍មេឌៀ"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"បានពង្រីកគ្រាប់រំកិលកម្រិតសំឡេង"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"បានបង្រួមគ្រាប់រំកិលកម្រិតសំឡេង"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"បិទសំឡេង %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"បើកសំឡេង %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"កំពុងចាក់ <xliff:g id="LABEL">%s</xliff:g> នៅលើ"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"សំឡេងនឹងលេងនៅលើ"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"កំពុងនិយាយទូរសព្ទ"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"កម្មវិធីសម្រួល UI ប្រព័ន្ធ"</string> <string name="status_bar" msgid="4357390266055077437">"របារស្ថានភាព"</string> <string name="demo_mode" msgid="263484519766901593">"មុខងារសាកល្បង UI ប្រព័ន្ធ"</string> diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index e217f8e20470..46a679cff801 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -368,7 +368,7 @@ <string name="quick_settings_contrast_standard" msgid="2538227821968061832">"ಪ್ರಮಾಣಿತ"</string> <string name="quick_settings_contrast_medium" msgid="5158352575583902566">"ಮಧ್ಯಮ"</string> <string name="quick_settings_contrast_high" msgid="656049259587494499">"ಹೆಚ್ಚು"</string> - <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"ಹಿಯರಿಂಗ್ ಸಾಧನಗಳು"</string> + <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"ಶ್ರವಣ ಸಾಧನಗಳು"</string> <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"ಹಿಯರಿಂಗ್ ಸಾಧನಗಳು"</string> <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"ಹೊಸ ಸಾಧನವನ್ನು ಪೇರ್ ಮಾಡಿ"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"ಹೊಸ ಸಾಧನವನ್ನು ಜೋಡಿಸಲು ಕ್ಲಿಕ್ ಮಾಡಿ"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ವಿಜೆಟ್ಗಳನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಲು ದೀರ್ಘಕಾಲ ಒತ್ತಿರಿ"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ವಿಜೆಟ್ಗಳನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಿ"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾದ ವಿಜೆಟ್ಗಾಗಿ ಆ್ಯಪ್ ಐಕಾನ್"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"ವಿಜೆಟ್ ಅನ್ನು ಎಡಿಟ್ ಮಾಡಿ"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"ತೆಗೆದುಹಾಕಿ"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ವಿಜೆಟ್ ಅನ್ನು ಸೇರಿಸಿ"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ವಿಜೆಟ್ಗಳನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಿ"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ಲಾಕ್ ಸ್ಕ್ರೀನ್ನಲ್ಲಿ ವಿಜೆಟ್ಗಳು"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ವಿಜೆಟ್ ಅನ್ನು ಆಯ್ಕೆ ಮಾಡಿ"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ವಿಜೆಟ್ ಅನ್ನು ತೆಗೆದುಹಾಕಿ"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ಆಯ್ಕೆಮಾಡಿದ ವಿಜೆಟ್ ಅನ್ನು ಇರಿಸಿ"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ಬಳಕೆದಾರರನ್ನು ಬದಲಿಸಿ"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ಪುಲ್ಡೌನ್ ಮೆನು"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ಈ ಸೆಶನ್ನಲ್ಲಿನ ಎಲ್ಲಾ ಆ್ಯಪ್ಗಳು ಮತ್ತು ಡೇಟಾವನ್ನು ಅಳಿಸಲಾಗುತ್ತದೆ."</string> @@ -594,9 +594,9 @@ <string name="screen_pinning_negative" msgid="6882816864569211666">"ಬೇಡ"</string> <string name="screen_pinning_start" msgid="7483998671383371313">"ಆ್ಯಪ್ ಪಿನ್ ಮಾಡಲಾಗಿದೆ"</string> <string name="screen_pinning_exit" msgid="4553787518387346893">"ಆ್ಯಪ್ ಅನ್ಪಿನ್ ಮಾಡಲಾಗಿದೆ"</string> - <string name="stream_voice_call" msgid="7468348170702375660">"ಕರೆಮಾಡಿ"</string> + <string name="stream_voice_call" msgid="7468348170702375660">"ಕರೆ ಮಾಡಿ"</string> <string name="stream_system" msgid="7663148785370565134">"ಸಿಸ್ಟಂ"</string> - <string name="stream_ring" msgid="7550670036738697526">"ರಿಂಗ್"</string> + <string name="stream_ring" msgid="7550670036738697526">"ರಿಂಗ್ ಮಾಡಿ"</string> <string name="stream_music" msgid="2188224742361847580">"ಮಾಧ್ಯಮ"</string> <string name="stream_alarm" msgid="16058075093011694">"ಅಲಾರಮ್"</string> <string name="stream_notification" msgid="7930294049046243939">"ನೋಟಿಫಿಕೇಶನ್"</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"ಔಟ್ಪುಟ್ ಸೆಟ್ಟಿಂಗ್ಗಳನ್ನು ಪ್ರವೇಶಿಸಿ"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ವಾಲ್ಯೂಮ್ ಸ್ಲೈಡರ್ಗಳನ್ನು ವಿಸ್ತೃತಗೊಳಿಸಲಾಗಿದೆ"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ವಾಲ್ಯೂಮ್ ಸ್ಲೈಡರ್ಗಳನ್ನು ಕುಗ್ಗಿಸಲಾಗಿದೆ"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s ಮ್ಯೂಟ್ ಮಾಡಿ"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s ಅನ್ಮ್ಯೂಟ್ ಮಾಡಿ"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> ನಲ್ಲಿ ಪ್ಲೇ ಆಗು..."</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"ಇದರಲ್ಲಿ ಪ್ಲೇ ಆಗುತ್ತದೆ"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"ಕರೆ ಮಾಡಲಾಗುತ್ತಿದೆ"</string> diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index 3ef3dc862bfb..54dcfce6d5b8 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"위젯을 맞춤설정하려면 길게 누르기"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"위젯 맞춤설정"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"사용 중지된 위젯의 앱 아이콘"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"위젯 수정"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"삭제"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"위젯 추가"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"위젯 맞춤설정"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"잠금 화면의 위젯"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"위젯 선택"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"위젯 삭제"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"선택한 위젯 배치"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"사용자 전환"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"풀다운 메뉴"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"이 세션에 있는 모든 앱과 데이터가 삭제됩니다."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"출력 설정 열기"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"볼륨 슬라이더 펼침"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"볼륨 슬라이더 접힘"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s 음소거"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s 음소거 해제"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> 재생 위치:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"오디오 재생 위치:"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"전화 거는 중"</string> diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index a75d0d5fe2e7..a224ac78d599 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Виджеттерди ыңгайлаштыруу үчүн кое бербей басып туруңуз"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Виджеттерди ыңгайлаштыруу"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Өчүрүлгөн виджет үчүн колдонмонун сүрөтчөсү"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Виджетти түзөтүү"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Өчүрүү"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Виджет кошуу"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Виджеттерди ыңгайлаштыруу"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Кулпуланган экрандагы виджеттер"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"виджет тандоо"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"виджетти алып салуу"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"тандалган виджетти жайгаштыруу"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Колдонуучуну которуу"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ылдый түшүүчү меню"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Бул сеанстагы бардык колдонмолор жана аларга байланыштуу нерселер өчүрүлөт."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Чыгаруу параметрлерин киргизүү"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Үндүн катуулугунун сыдырмалары жайып көрсөтүлдү"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Үндүн катуулугунун сыдырмалары жыйыштырылды"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s үнүн басуу"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s үнүн чыгаруу"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> аркылуу ойнотулууда"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Аудио кайсы жерде ойнотулат:"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Чалууда"</string> diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index eca041e8acf1..88866e37d07a 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ກົດຄ້າງໄວ້ເພື່ອປັບແຕ່ງວິດເຈັດ"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ປັບແຕ່ງວິດເຈັດ"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ໄອຄອນແອັບສຳລັບວິດເຈັດທີ່ຖືກປິດການນຳໃຊ້"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"ແກ້ໄຂວິດເຈັດ"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"ລຶບອອກ"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ເພີ່ມວິດເຈັດ"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ປັບແຕ່ງວິດເຈັດ"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ວິດເຈັດຢູ່ໜ້າຈໍລັອກ"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ເລືອກວິດເຈັດ"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ລຶບວິດເຈັດອອກ"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ວາງວິດເຈັດທີ່ເລືອກ"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ສະຫຼັບຜູ້ໃຊ້"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ເມນູແບບດຶງລົງ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ແອັບຯແລະຂໍ້ມູນທັງໝົດໃນເຊດຊັນນີ້ຈະຖືກລຶບອອກ."</string> @@ -629,10 +629,16 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"ໃສ່ການຄັ້ງຄ່າເອົ້າພຸດ"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ຂະຫຍາຍສະໄລເດີລະດັບສຽງແລ້ວ"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ຫຍໍ້ສະໄລເດີລະດັບສຽງລົງແລ້ວ"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"ປິດສຽງ %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"ເຊົາປິດສຽງ %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"ກຳລັງຫຼິ້ນ <xliff:g id="LABEL">%s</xliff:g> ໃນ"</string> - <string name="media_output_title_without_playing" msgid="3825663683169305013">"ສຽງຈະຫຼິ້ນຕໍ່ໄປ"</string> + <string name="media_output_title_without_playing" msgid="3825663683169305013">"ສຽງຈະຫຼິ້ນຢູ່"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"ກຳລັງໂທ"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"ແຖບສະຖານະ"</string> diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index fa5790ce8e26..46ac3044f765 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Ilgai paspauskite, kad tinkintumėte valdiklius"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Tinkinti valdiklius"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Išjungto valdiklio programos piktograma"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Redaguoti valdiklį"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Pašalinti"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Pridėti valdiklį"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Tinkinti valdiklius"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Valdikliai užrakinimo ekrane"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"pasirinkite valdiklį"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"pašalinti valdiklį"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"padėti pasirinktą valdiklį"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Perjungti naudotoją"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"išplečiamasis meniu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bus ištrintos visos šios sesijos programos ir duomenys."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Įveskite išvesties nustatymus"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Garsumo šliaužikliai išskleisti"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Garsumo šliaužikliai sutraukti"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"nutildyti %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"įjungti garsą %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Leidžiama „<xliff:g id="LABEL">%s</xliff:g>“"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Garsas bus leidžiamas"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Skambinama"</string> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index 9e01526dcb52..979f59e3374f 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -370,7 +370,7 @@ <string name="quick_settings_contrast_high" msgid="656049259587494499">"Augsts"</string> <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"Dzirdes aparāti"</string> <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"Dzirdes aparāti"</string> - <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Savienojiet pārī jaunu ierīci"</string> + <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Savienot pārī jaunu ierīci"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Noklikšķiniet, lai savienotu pārī jaunu ierīci"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Nevarēja atjaunināt pirmsiestatījumu"</string> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Vai atbloķēt ierīces mikrofonu?"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Nospiediet un turiet, lai pielāgotu logrīkus."</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Pielāgot logrīkus"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Lietotnes ikona atspējotam logrīkam"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Rediģēt logrīku"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Noņemt"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Pievienot logrīku"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Pielāgot logrīkus"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Logrīki bloķēšanas ekrānā"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"atlasīt logrīku"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"noņemt logrīku"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"novietot atlasīto logrīku"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Mainīt lietotāju"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"novelkamā izvēlne"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tiks dzēstas visas šīs sesijas lietotnes un dati."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Atvērt izvades iestatījumus"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Skaļuma slīdņi izvērsti"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Skaļuma slīdņi sakļauti"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"izslēgt skaņu straumei %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"ieslēgt skaņu straumei %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> — atskaņošana šeit:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio tiks atskaņots šeit:"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Aktīvs zvans ierīcē"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Sistēmas saskarnes regulators"</string> <string name="status_bar" msgid="4357390266055077437">"Statusa josla"</string> <string name="demo_mode" msgid="263484519766901593">"Sistēmas lietotāja saskarnes demonstrācijas režīms"</string> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index d1e26e5d8565..e305c9e5fee1 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Притиснете долго за да ги приспособите виџетите"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Приспособете ги виџетите"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Икона за апликација за оневозможен виџет"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Изменување виџети"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Отстранува"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Додајте виџет"</string> @@ -596,14 +598,14 @@ <string name="screen_pinning_exit" msgid="4553787518387346893">"Апликацијата е откачена"</string> <string name="stream_voice_call" msgid="7468348170702375660">"Повик"</string> <string name="stream_system" msgid="7663148785370565134">"Систем"</string> - <string name="stream_ring" msgid="7550670036738697526">"Ѕвони"</string> + <string name="stream_ring" msgid="7550670036738697526">"Ѕвонење"</string> <string name="stream_music" msgid="2188224742361847580">"Аудиовизуелни содржини"</string> <string name="stream_alarm" msgid="16058075093011694">"Аларм"</string> <string name="stream_notification" msgid="7930294049046243939">"Известување"</string> <string name="stream_bluetooth_sco" msgid="6234562365528664331">"Bluetooth"</string> <string name="stream_dtmf" msgid="7322536356554673067">"Двојна повеќетонска фреквенција"</string> <string name="stream_accessibility" msgid="3873610336741987152">"Пристапност"</string> - <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ѕвони"</string> + <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Ѕвонење"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Вибрации"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Исклучи звук"</string> <string name="media_device_cast" msgid="4786241789687569892">"Емитување"</string> @@ -629,12 +631,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Внесете ги поставките за излез"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Лизгачите за јачина на звукот се проширени"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Лизгачите за јачина на звукот се собрани"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"исклучување звук на %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"вклучување звук на %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g>: пуштено на"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Аудиото ќе се пушти на"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Повик во тек"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Адаптер на УИ на системот"</string> <string name="status_bar" msgid="4357390266055077437">"Статусна лента"</string> <string name="demo_mode" msgid="263484519766901593">"Демо-режим на кориснички интерфејс на систем"</string> diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index 79be0f17fb5a..c14cc418d5a8 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"വിജറ്റുകൾ ഇഷ്ടാനുസൃതമാക്കാൻ ദീർഘനേരം അമർത്തുക"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"വിജറ്റുകൾ ഇഷ്ടാനുസൃതമാക്കുക"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"പ്രവർത്തനരഹിതമാക്കിയ വിജറ്റിനുള്ള ആപ്പ് ഐക്കൺ"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"വിജറ്റ് എഡിറ്റ് ചെയ്യുക"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"നീക്കം ചെയ്യുക"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"വിജറ്റ് ചേർക്കുക"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"വിജറ്റുകൾ ഇഷ്ടാനുസൃതമാക്കുക"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ലോക്ക് സ്ക്രീനിൽ വിജറ്റുകൾ"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"വിജറ്റ് തിരഞ്ഞെടുക്കുക"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"വിജറ്റ് നീക്കം ചെയ്യുക"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"തിരഞ്ഞെടുത്ത വിജറ്റ് നൽകുക"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ഉപയോക്താവ് മാറുക"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"പുൾഡൗൺ മെനു"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ഈ സെഷനിലെ എല്ലാ ആപ്പുകളും ഡാറ്റയും ഇല്ലാതാക്കും."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"ഔട്ട്പുട്ട് ക്രമീകരണം നൽകുക"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"വോളിയം സ്ലൈഡറുകൾ വികസിപ്പിച്ചു"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"വോളിയം സ്ലൈഡറുകൾ ചുരുക്കി"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s മ്യൂട്ട് ചെയ്യുക"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s അൺമ്യൂട്ട് ചെയ്യുക"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> എന്നതിൽ പ്ലേ ചെയ്യുന്നു"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"ഓഡിയോ പ്ലേ ചെയ്യും"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"കോൾ പുരോഗമിക്കുന്നു"</string> diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index 93304d64e511..573c8ff7e7d6 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Виджетүүдийг өөрчлөхийн тулд удаан дарна уу"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Виджетүүдийг өөрчлөх"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Идэвхгүй болгосон виджетийн аппын дүрс тэмдэг"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Виджетийг засах"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Хасах"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Виджет нэмэх"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Виджетийг өөрчлөх"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Түгжээтэй дэлгэц дээрх виджетүүд"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"виджет сонгох"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"виджетийг хасах"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"сонгосон виджетийг байрлуулах"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Хэрэглэгчийг сэлгэх"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"эвхмэл цэс"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Энэ харилцан үйлдлийн бүх апп болон дата устах болно."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Оролтын тохиргоог оруулах"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Дууны түвшний гулсуулагчдыг дэлгэсэн"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Дууны түвшний гулсуулагчдыг хураасан"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s-н дууг хаах"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s-н дууг нээх"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> тоглуулж байна"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Аудиог дараахад тоглуулна"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Дуудлага хийгдэж буй:"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Системийн UI Тохируулагч"</string> <string name="status_bar" msgid="4357390266055077437">"Статус самбар"</string> <string name="demo_mode" msgid="263484519766901593">"Системийн UI демо горим"</string> diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index 0aad76430e82..3667f1e9dcd1 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"विजेट कस्टमाइझ करण्यासाठी प्रेस करून ठेवा"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"विजेट कस्टमाइझ करा"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"बंद केलेल्या विजेटच्या अॅपचे आयकन"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"विजेट संपादित करा"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"काढून टाका"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"विजेट जोडा"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"विजेट कस्टमाइझ करा"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"लॉक स्क्रीनवरील विजेट"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"विजेट निवडा"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"विजेट काढून टाका"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"निवडलेले विजेट ठेवा"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"वापरकर्ता स्विच करा"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेनू"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"या सत्रातील सर्व अॅप्स आणि डेटा हटवला जाईल."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"आउटपुट सेटिंग्ज एंटर करा"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"व्हॉल्यूम स्लायडर विस्तारित केले आहेत"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"व्हॉल्यूम स्लायडर कोलॅप्स केले आहेत"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s म्यूट करा"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s अनम्यूट करा"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> वर प्ले करत आहे"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"यावर ऑडिओ प्ले होईल"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"यावर कॉल करत आहे"</string> diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index 60c028d40869..60aedfa449cf 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -275,7 +275,7 @@ <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Disimpan"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"putuskan sambungan"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktifkan"</string> - <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Dihidupkan sekali lagi esok secara automatik"</string> + <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Dihidupkan lagi esok secara automatik"</string> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Ciri seperti Quick Share dan Find My Device menggunakan Bluetooth"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth akan dihidupkan esok pagi"</string> <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"Perkongsian Audio"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Tekan lama untuk menyesuaikan widget"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Sesuaikan widget"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikon apl untuk melumpuhkan widget"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Edit widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Alih keluar"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Tambahkan widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Sesuaikan widget"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widget pada skrin kunci"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"pilih widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"alih keluar widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"letakkan widget dipilih"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Tukar pengguna"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu tarik turun"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Semua apl dan data dalam sesi ini akan dipadam."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Masukkan tetapan output"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Peluncur kelantangan dikembangkan"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Peluncur kelantangan dikuncupkan"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"redamkan %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"nyahredamkan %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Memainkan <xliff:g id="LABEL">%s</xliff:g> pada"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio dimainkan pada"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Membuat panggilan"</string> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index dcbf837df5ec..b7398d0efa13 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ဝိဂျက်များ စိတ်ကြိုက်လုပ်ရန် ကြာကြာနှိပ်ထားပါ"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ဝိဂျက်များကို စိတ်ကြိုက်လုပ်ရန်"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ပိတ်ထားသော ဝိဂျက်အတွက် အက်ပ်သင်္ကေတ"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"ဝိဂျက်ပြင်ရန်"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"ဖယ်ရှားရန်"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ဝိဂျက်ထည့်ရန်"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ဝိဂျက်များကို စိတ်ကြိုက်လုပ်ရန်"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"လော့ခ်မျက်နှာပြင်ရှိ ဝိဂျက်များ"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ဝိဂျက် ရွေးရန်"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ဝိဂျက် ဖယ်ရှားရန်"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ရွေးချယ်ထားသော ဝိဂျက်ကို တင်ရန်"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"အသုံးပြုသူကို ပြောင်းလဲရန်"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ဆွဲချမီနူး"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ဒီချိတ်ဆက်မှု ထဲက အက်ပ်များ အားလုံး နှင့် ဒေတာကို ဖျက်ပစ်မည်။"</string> @@ -619,7 +619,7 @@ <string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"ထောင့်စုံအော်ဒီယို"</string> <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"ပိတ်"</string> <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"ပုံသေ"</string> - <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"ခေါင်းလှုပ်ရှားမှု စောင့်ကြည့်ခြင်း"</string> + <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"ခေါင်းလှုပ်ရှားမှု"</string> <string name="volume_ringer_change" msgid="3574969197796055532">"ဖုန်းခေါ်သံမုဒ်သို့ ပြောင်းရန် တို့ပါ"</string> <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"အသံပိတ်ရန်"</string> <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"အသံဖွင့်ရန်"</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"အထွက် ဆက်တင်များ ထည့်ရန်"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"အသံအတိုးအကျယ် ရွှေ့တုံးများ ပိုပြထားသည်"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"အသံအတိုးအကျယ် ရွှေ့တုံးများ လျှော့ပြထားသည်"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s အသံပိတ်ရန်"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s အသံပြန်ဖွင့်ရန်"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> ဖွင့်မည့်နေရာ"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"အသံဖွင့်မည့်နေရာ"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"ဖုန်းဆက်နေသည်"</string> diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 2724a5174752..2b131c7e5407 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Trykk lenge for å tilpasse modulene"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Tilpass moduler"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Appikon for deaktivert modul"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Endre modul"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Fjern"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Legg til modul"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Tilpass moduler"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Moduler på låseskjermen"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"velg modul"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"fjern modul"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"plasser den valgte modulen"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Bytt bruker"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullegardinmeny"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apper og data i denne økten blir slettet."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Angi utdatainnstillinger"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Glidebrytere for volum er skjult"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Glidebrytere for volum er skjult"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"kutt lyden til %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"slå på lyden til %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Spiller av <xliff:g id="LABEL">%s</xliff:g> på"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Lyden spilles av på"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Aktiv samtale på"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Statusrad"</string> <string name="demo_mode" msgid="263484519766901593">"Demomodus for systemgrensesnitt"</string> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index 1bbab4ae9736..42abd4439421 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -203,10 +203,10 @@ <string name="face_reenroll_failure_dialog_content" msgid="7073947334397236935">"फेस अनलक सेटअप गर्न सकिएन। फेरि प्रयास गर्न सेटिङमा जानुहोस्।"</string> <string name="fingerprint_dialog_touch_sensor" msgid="2817887108047658975">"फिंगरप्रिन्ट सेन्सरमा छुनुहोस्"</string> <string name="fingerprint_dialog_authenticated_confirmation" msgid="1603899612957562862">"जारी राख्न अनलक आइकनमा थिच्नुहोस्"</string> - <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"अनुहार पहिचान गर्न सकिएन। बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्।"</string> + <string name="fingerprint_dialog_use_fingerprint_instead" msgid="5542430577183894219">"अनुहार मिलेन। बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्।"</string> <!-- no translation found for keyguard_face_failed_use_fp (7140293906176164263) --> <skip /> - <string name="keyguard_face_failed" msgid="2346762871330729634">"अनुहार पहिचान गर्न सकिएन"</string> + <string name="keyguard_face_failed" msgid="2346762871330729634">"अनुहार मिलेन"</string> <string name="keyguard_suggest_fingerprint" msgid="8742015961962702960">"बरु फिंगरप्रिन्ट प्रयोग गर्नुहोस्"</string> <string name="keyguard_face_unlock_unavailable" msgid="1581949044193418736">"फेस अनलक उपलब्ध छैन"</string> <string name="accessibility_bluetooth_connected" msgid="4745196874551115205">"ब्लुटुथ जडान भयो।"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"विजेटहरू कस्टमाइज गर्न केही बेरसम्म थिच्नुहोस्"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"विजेटहरू कस्टमाइज गर्नुहोस्"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"अफ गरिएको विजेटको एप जनाउने आइकन"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"विजेट सम्पादन गर्नुहोस्"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"हटाउनुहोस्"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"विजेट हाल्नुहोस्"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"विजेटहरू कस्टमाइज गर्नुहोस्"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"लक स्क्रिनमा भएका विजेटहरू"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"विजेट चयन गर्नुहोस्"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"विजेट हटाउनुहोस्"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"चयन गरिएका विजेटका लागि ठाउँ चयन गर्नुहोस्"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"प्रयोगकर्ता फेर्नुहोस्"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"पुलडाउन मेनु"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"यो सत्रमा भएका सबै एपहरू र डेटा मेटाइने छ।"</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"आउटपुटसम्बन्धी सेटिङमा जानुहोस्"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"भोल्युम स्लाइडरहरू एक्स्पान्ड गरिएका छन्"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"भोल्युम स्लाइडरहरू कोल्याप्स गरिएका छन्"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s म्युट गर्नुहोस्"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s अनम्युट गर्नुहोस्"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> प्ले गरिँदै छ"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"अडियो यसमा प्ले हुने छ"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"कल चलिरहेको छ"</string> diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 1a831ac735f2..c459b93e87cb 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Houd lang ingedrukt om widgets aan te passen"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Widgets aanpassen"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"App-icoon voor uitgezette widget"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Widget bewerken"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Verwijderen"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Widget toevoegen"</string> @@ -619,7 +621,7 @@ <string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"Ruimtelijke audio"</string> <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Uit"</string> <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Vast"</string> - <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Hoofdbeweging volgen"</string> + <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Hoofdtracking"</string> <string name="volume_ringer_change" msgid="3574969197796055532">"Tik om de beltoonmodus te wijzigen"</string> <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"geluid uit"</string> <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"geluid aanzetten"</string> @@ -629,8 +631,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Uitvoerinstellingen invoeren"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volumeschuifregelaars uitgevouwen"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volumeschuifregelaars samengevouwen"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"geluid van %s uitzetten"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"geluid van %s aanzetten"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> wordt afgespeeld op"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio wordt afgespeeld op"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Bellen actief"</string> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index e64b1b47eaa8..1444e6d69bc3 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -370,7 +370,7 @@ <string name="quick_settings_contrast_high" msgid="656049259587494499">"ଅଧିକ"</string> <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"ହିଅରିଂ ଡିଭାଇସଗୁଡ଼ିକ"</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="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"ଡିଭାଇସର ମାଇକ୍ରୋଫୋନକୁ ଅନବ୍ଲକ କରିବେ?"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ୱିଜେଟଗୁଡ଼ିକୁ କଷ୍ଟମାଇଜ କରିବା ପାଇଁ ଅଧିକ ସମୟ ଦବାନ୍ତୁ"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ୱିଜେଟଗୁଡ଼ିକୁ କଷ୍ଟମାଇଜ କରନ୍ତୁ"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ଅକ୍ଷମ କରାଯାଇଥିବା ୱିଜେଟ ପାଇଁ ଆପ ଆଇକନ"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"ୱିଜେଟକୁ ଏଡିଟ କରନ୍ତୁ"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"କାଢ଼ି ଦିଅନ୍ତୁ"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ୱିଜେଟ ଯୋଗ କରନ୍ତୁ"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ୱିଜେଟଗୁଡ଼ିକୁ କଷ୍ଟମାଇଜ କରନ୍ତୁ"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ଲକ ସ୍କ୍ରିନରେ ଥିବା ୱିଜେଟଗୁଡ଼ିକ"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ୱିଜେଟ ଚୟନ କରନ୍ତୁ"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ୱିଜେଟକୁ କାଢ଼ି ଦିଅନ୍ତୁ"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ଚୟନିତ ୱିଜେଟ ରଖନ୍ତୁ"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ୟୁଜର୍ ବଦଳାନ୍ତୁ"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ପୁଲଡାଉନ ମେନୁ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ଏହି ସେସନର ସମସ୍ତ ଆପ୍ ଓ ଡାଟା ଡିଲିଟ୍ ହୋଇଯିବ।"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"ଆଉଟପୁଟ ସେଟିଂସ ଲେଖନ୍ତୁ"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ଭଲ୍ୟୁମ ସ୍ଲାଇଡରଗୁଡ଼ିକୁ ବିସ୍ତାର କରାଯାଇଛି"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ଭଲ୍ୟୁମ ସ୍ଲାଇଡରଗୁଡ଼ିକୁ ସଙ୍କୁଚିତ କରାଯାଇଛି"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%sକୁ ମ୍ୟୁଟ କରନ୍ତୁ"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%sକୁ ଅନମ୍ୟୁଟ କରନ୍ତୁ"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g>ରେ ପ୍ଲେ କରାଯାଉଛି"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"ଅଡିଓ ଏଥିରେ ପ୍ଲେ ହେବ"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"କଲ ଚାଲିଛି"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"ସିଷ୍ଟମ୍ UI ଟ୍ୟୁନର୍"</string> <string name="status_bar" msgid="4357390266055077437">"ଷ୍ଟାଟସ୍ ବାର୍"</string> <string name="demo_mode" msgid="263484519766901593">"ସିଷ୍ଟମ୍ UI ଡେମୋ ମୋଡ୍"</string> diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index 394d790ecbdb..23914b3f16c6 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ਵਿਜੇਟਾਂ ਨੂੰ ਵਿਉਂਤਬੱਧ ਕਰਨ ਲਈ ਦਬਾਈ ਰੱਖੋ"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ਵਿਜੇਟ ਵਿਉਂਤਬੱਧ ਕਰੋ"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ਬੰਦ ਵਿਜੇਟ ਲਈ ਐਪ ਪ੍ਰਤੀਕ"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"ਵਿਜੇਟ ਦਾ ਸੰਪਾਦਨ ਕਰੋ"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"ਹਟਾਓ"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ਵਿਜੇਟ ਸ਼ਾਮਲ ਕਰੋ"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ਵਿਜੇਟ ਵਿਉਂਤਬੱਧ ਕਰੋ"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"ਲਾਕ ਸਕ੍ਰੀਨ \'ਤੇ ਵਿਜੇਟ"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ਵਿਜੇਟ ਚੁਣੋ"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ਵਿਜੇਟ ਹਟਾਓ"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ਚੁਣੇ ਗਏ ਵਿਜੇਟ ਲਈ ਥਾਂ ਚੁਣੋ"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"ਵਰਤੋਂਕਾਰ ਸਵਿੱਚ ਕਰੋ"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"ਪੁੱਲਡਾਊਨ ਮੀਨੂ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ਇਸ ਸੈਸ਼ਨ ਵਿਚਲੀਆਂ ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਡਾਟੇ ਨੂੰ ਮਿਟਾ ਦਿੱਤਾ ਜਾਵੇਗਾ।"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"ਆਊਟਪੁੱਟ ਸੈਟਿੰਗਾਂ ਦਾਖਲ ਕਰੋ"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ਅਵਾਜ਼ ਸਲਾਈਡਰਾਂ ਵਿਸਤਾਰ ਕੀਤਾ ਗਿਆ"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ਅਵਾਜ਼ ਸਲਾਈਡਰਾਂ ਨੂੰ ਸਮੇਟਿਆ ਗਿਆ"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s ਨੂੰ ਮਿਊਟ ਕਰੋ"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s ਨੂੰ ਅਣਮਿਊਟ ਕਰੋ"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> ਚਲਾਇਆ ਜਾ ਰਿਹਾ ਹੈ"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"ਆਡੀਓ ਇਸ \'ਤੇ ਚੱਲੇਗੀ"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"ਕਾਲ ਜਾਰੀ ਹੈ"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI ਟਿਊਨਰ"</string> <string name="status_bar" msgid="4357390266055077437">"ਸਥਿਤੀ ਪੱਟੀ"</string> <string name="demo_mode" msgid="263484519766901593">"ਸਿਸਟਮ UI ਡੈਮੋ ਮੋਡ"</string> diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index 920862837eb9..4609f08ca0e0 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Przytrzymaj, aby dostosować widżety"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Dostosuj widżety"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacji z wyłączonym widżetem"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Edytuj widżet"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Usuń"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodaj widżet"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Dostosuj widżety"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widżety na ekranie blokady"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"wybierz widżet"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"usuń widżet"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"umieść wybrany widżet"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Przełącz użytkownika"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Wszystkie aplikacje i dane w tej sesji zostaną usunięte."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Otwórz ustawienia sygnału wyjściowego"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Suwaki głośności są rozwinięte"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Suwaki głośności są zwinięte"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"wycisz: %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"wyłącz wyciszenie: %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Odtwarzam <xliff:g id="LABEL">%s</xliff:g> na"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Wyjścia dźwięku:"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Dzwonię na:"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Kalibrator System UI"</string> <string name="status_bar" msgid="4357390266055077437">"Pasek stanu"</string> <string name="demo_mode" msgid="263484519766901593">"Tryb demonstracyjny interfejsu"</string> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index 60472c8bcb34..425fa65b00fc 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantenha pressionado para personalizar widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ícone do app para widget desativado"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Remover"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adicionar widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizar widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets na tela de bloqueio"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"selecionar widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"remover widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"posicionar widget selecionado"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Trocar usuário"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu suspenso"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todos os apps e dados nesta sessão serão excluídos."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Inserir configurações de saída"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Controles deslizantes de volume abertos"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Controles deslizantes de volume fechados"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"desativar o som de %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"ativar o som de %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Tocando <xliff:g id="LABEL">%s</xliff:g> em"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Onde o áudio vai tocar?"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Ligando"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Sintonizador System UI"</string> <string name="status_bar" msgid="4357390266055077437">"Barra de status"</string> <string name="demo_mode" msgid="263484519766901593">"Modo de demonstração da interface do sistema"</string> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 3b9bebb784d8..44531331bd68 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantenha premido para personalizar os widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ícone da app do widget desativado"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Remover"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adicionar widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizar widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets no ecrã de bloqueio"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"selecionar widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"remover widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"posicionar widget selecionado"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Mudar utilizador"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu pendente"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todas as apps e dados desta sessão serão eliminados."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Introduzir definições de saída"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Controlos de deslize do volume expandidos"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Controlos de deslize do volume reduzidos"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"desativar o som de %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"reativar o som de %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"A ouvir <xliff:g id="LABEL">%s</xliff:g> em:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Áudio ouvido em:"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Chamada em curso"</string> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index 60472c8bcb34..425fa65b00fc 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Mantenha pressionado para personalizar widgets"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizar widgets"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ícone do app para widget desativado"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Editar widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Remover"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adicionar widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizar widgets"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets na tela de bloqueio"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"selecionar widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"remover widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"posicionar widget selecionado"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Trocar usuário"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu suspenso"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Todos os apps e dados nesta sessão serão excluídos."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Inserir configurações de saída"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Controles deslizantes de volume abertos"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Controles deslizantes de volume fechados"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"desativar o som de %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"ativar o som de %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Tocando <xliff:g id="LABEL">%s</xliff:g> em"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Onde o áudio vai tocar?"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Ligando"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Sintonizador System UI"</string> <string name="status_bar" msgid="4357390266055077437">"Barra de status"</string> <string name="demo_mode" msgid="263484519766901593">"Modo de demonstração da interface do sistema"</string> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index 84d68e70037c..596349ee57f1 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Apasă lung pentru a personaliza widgeturi"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizează widgeturile"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Pictograma aplicației pentru widgetul dezactivat"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Editează widgetul"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Elimină"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Adaugă un widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizează widgeturile"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgeturi pe ecranul de blocare"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"selectează un widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"elimină widgetul"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"plasează widgetul selectat"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Schimbă utilizatorul"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"meniu vertical"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Toate aplicațiile și datele din această sesiune vor fi șterse."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Introdu setările de ieșire"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Glisoare de volum extinse"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Glisoare de volum restrânse"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"dezactivează sunetul pentru %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"activează sunetul pentru %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Se redă <xliff:g id="LABEL">%s</xliff:g> pe"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Conținutul audio se va reda pe"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Apel în curs pe"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Bară de stare"</string> <string name="demo_mode" msgid="263484519766901593">"Mod demonstrativ pentru IU sistem"</string> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index c405469371da..6fabeae41a4a 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Нажмите и удерживайте, чтобы настроить виджеты."</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Настроить виджеты"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Значок приложения для отключенного виджета"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Изменить виджет"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Удалить"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Добавить виджет"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Настроить виджеты"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Виджеты на заблокированном экране"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"выбрать виджет"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"удалить виджет"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"разместить выбранный виджет"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Сменить пользователя."</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"раскрывающееся меню"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Все приложения и данные этого профиля будут удалены."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Перейти к настройкам вывода аудио"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Ползунки для регулировки громкости развернуты"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Ползунки для регулировки громкости свернуты"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"отключить звук: %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"включить звук: %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> – запущено здесь:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Проигрывание аудио:"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Настройки вызова"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Строка состояния"</string> <string name="demo_mode" msgid="263484519766901593">"Интерфейс системы: деморежим"</string> diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index 37bb23ac64a7..238e6c8e2b80 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"විජට් අභිරුචිකරණය කිරීමට දිගු ඔබන්න"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"විජට්ටු අභිරුචි කරන්න"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"අබල කළ විජට් සඳහා යෙදුම් නිරූපකය"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"විජට්ටු සංස්කරණ කරන්න"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"ඉවත් කරන්න"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"විජට්ටුව එක් කරන්න"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"විජට්ටු අභිරුචි කරන්න"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"අගුළු තිරයෙහි විජට්"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"විජට්ටුව තෝරන්න"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"විජට්ටුව ඉවත් කරන්න"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"තෝරන ලද විජට්ටුව තබන්න"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"පරිශීලක මාරුව"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"නිපතන මෙනුව"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"මෙම සැසියේ සියළුම යෙදුම් සහ දත්ත මකාවී."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"ප්රතිදාන සැකසීම් ඇතුල් කරන්න"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"හඬ ස්ලයිඩර දිගහැර ඇත"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"හඬ ස්ලයිඩර හකුළා ඇත"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s නිහඬ කරන්න"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s නිහඬ නොකරන්න"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> වාදනය කරන්නේ"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"ශ්රව්ය වාදනය වනු ඇත්තේ"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"ඇමතීම"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"පද්ධති UI සුසරකය"</string> <string name="status_bar" msgid="4357390266055077437">"තත්ත්ව තීරුව"</string> <string name="demo_mode" msgid="263484519766901593">"පද්ධති UI ආදර්ශන ප්රකාරය"</string> diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index 675a34522086..825dba620870 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -269,7 +269,7 @@ <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Klepnutím pripojíte alebo odpojíte zariadenie"</string> <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Spárovať nové zariadenie"</string> <string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Zobraziť všetko"</string> - <string name="turn_on_bluetooth" msgid="5681370462180289071">"Použiť Bluetooth"</string> + <string name="turn_on_bluetooth" msgid="5681370462180289071">"Používať Bluetooth"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Pripojené"</string> <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"Zdieľanie zvuku"</string> <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Uložené"</string> @@ -370,7 +370,7 @@ <string name="quick_settings_contrast_high" msgid="656049259587494499">"Vysoký"</string> <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"Načúvacie zariadenia"</string> <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"Načúvacie zariadenia"</string> - <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Párovanie nového zariadenia"</string> + <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Spárovať nové zariadenie"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Kliknutím spárujete nové zariadenie"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Predvoľbu sa nepodarilo aktualizovať"</string> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Chcete odblokovať mikrofón zariadenia?"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Miniaplikácie prispôsobíte dlhým stlačením"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prispôsobiť miniaplikácie"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona deaktivovanej miniaplikácie"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Upraviť miniaplikáciu"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Odstrániť"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Pridať miniaplikáciu"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Prispôsobiť miniaplikácie"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Miniaplikácie na uzamknutej obrazovke"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"vybrať miniaplikáciu"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"odstrániť miniaplikáciu"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"prepnúť vybranú miniaplikáciu"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Prepnutie používateľa"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rozbaľovacia ponuka"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Všetky aplikácie a údaje v tejto relácii budú odstránené."</string> @@ -619,7 +619,7 @@ <string name="volume_panel_spatial_audio_title" msgid="3367048857932040660">"Priestorový zvuk"</string> <string name="volume_panel_spatial_audio_off" msgid="4177490084606772989">"Vypnuté"</string> <string name="volume_panel_spatial_audio_fixed" msgid="3136080137827746046">"Pevné"</string> - <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Sled. pohybov hlavy"</string> + <string name="volume_panel_spatial_audio_tracking" msgid="5711115234001762974">"Sled. polohy hlavy"</string> <string name="volume_ringer_change" msgid="3574969197796055532">"Režim zvonenia zmeníte klepnutím"</string> <string name="volume_ringer_hint_mute" msgid="4263821214125126614">"vypnite zvuk"</string> <string name="volume_ringer_hint_unmute" msgid="6119086890306456976">"zapnite zvuk"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Zadať nastavenia výstupu"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Posúvače hlasitosti sú rozbalené"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Posúvače hlasitosti sú zbalené"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"vypnete zvuk %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"zapnete zvuk %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> sa prehráva v:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Zvuk sa prehrá cez"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Volanie je zapnuté"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Tuner používateľského rozhrania systému"</string> <string name="status_bar" msgid="4357390266055077437">"Stavový riadok"</string> <string name="demo_mode" msgid="263484519766901593">"Ukážka používateľského rozhrania systému"</string> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index f97d6624c00c..82f2df73a355 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pridržite za prilagajanje pripomočkov"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Prilagajanje pripomočkov"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona aplikacije za onemogočen pripomoček"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Urejanje pripomočka"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Odstrani"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Dodajanje pripomočka"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Prilagajanje pripomočkov"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Pripomočki na zaklenjenem zaslonu"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"izberite pripomoček"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"odstranitev pripomočka"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"postavitev izbranega pripomočka"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Preklop med uporabniki"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"spustni meni"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Vse aplikacije in podatki v tej seji bodo izbrisani."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Vnos izhodnih nastavitev"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Razširitev drsnikov za glasnost"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Strnitev drsnikov za glasnost"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"izklop zvoka %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"vklop zvoka %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Predvajanje »<xliff:g id="LABEL">%s</xliff:g>« v"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Zvok bo predvajan v"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Poteka klicanje"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Uglaševalnik uporabniškega vmesnika sistema"</string> <string name="status_bar" msgid="4357390266055077437">"Vrstica stanja"</string> <string name="demo_mode" msgid="263484519766901593">"Predstavitveni način uporabniškega vmesnika sistema"</string> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index b638198c6d5f..af2e3c788b5b 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Shtyp gjatë për të personalizuar miniaplikacionet"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Personalizo miniaplikacionet"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Ikona e aplikacionit për miniaplikacionin e çaktivizuar"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Modifiko miniaplikacionin"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Hiq"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Shto miniaplikacionin"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizo miniaplikacionet"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Miniaplikacionet në ekranin e kyçjes"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"zgjidh miniaplikacionin"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"hiq miniaplikacionin"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"vendos miniaplikacionin e zgjedhur"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Ndërro përdorues"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menyja me tërheqje poshtë"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Të gjitha aplikacionet dhe të dhënat në këtë sesion do të fshihen."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Hyr te cilësimet e daljes"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Rrëshqitësit e volumit u zgjeruan"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Rrëshqitësit e volumit u palosën"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"çaktivizo audion: %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"aktivizo audion: %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Po luhet <xliff:g id="LABEL">%s</xliff:g> te"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Do të luhet audio te"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Telefonatë aktive"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Sintonizuesi i Sistemit të Ndërfaqes së Përdoruesit"</string> <string name="status_bar" msgid="4357390266055077437">"Shiriti i statusit"</string> <string name="demo_mode" msgid="263484519766901593">"Modaliteti i demonstrimit i ndërfaqes së përdoruesit të sistemit"</string> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index 4cc1134a369a..42b979c0add9 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Дуги притисак за прилагођавање виџета"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Прилагоди виџете"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Икона апликације за онемогућен виџет"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Измени виџет"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Уклони"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Додај виџет"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Прилагодите виџете"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Виџети на закључаном екрану"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"изаберите виџет"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"уклоните виџет"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"поставите изабрани виџет"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Замени корисника"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"падајући мени"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Све апликације и подаци у овој сесији ће бити избрисани."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Унесите подешавања излазног сигнала"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Клизачи за јачину звука су проширени"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Клизачи за јачину звука су скупљени"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"искључите звук за: %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"укључите звук за: %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> се пушта на"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Звук се пушта на"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Позив на уређају"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Тјунер за кориснички интерфејс система"</string> <string name="status_bar" msgid="4357390266055077437">"Статусна трака"</string> <string name="demo_mode" msgid="263484519766901593">"Режим демонстрације за кориснички интерфејс система"</string> diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index b0ce12f8e5a2..29dca4630df8 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Tryck länge för att anpassa widgetar"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Anpassa widgetar"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Appikon för inaktiverad widget"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Redigera widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Ta bort"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Lägg till widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Anpassa widgetar"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgetar på låsskärmen"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"välj widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ta bort widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"placera vald widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Byt användare"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"rullgardinsmeny"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alla appar och data i denna session kommer att raderas."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Ange inställningar för utdata"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Volymreglagen har utökats"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Volymreglagen har komprimerats"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"stäng av ljudet för %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"slå på ljudet för %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Spelar upp <xliff:g id="LABEL">%s</xliff:g> på"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Ljud spelas upp på"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Samtal på"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Inställningar för systemgränssnitt"</string> <string name="status_bar" msgid="4357390266055077437">"Statusfält"</string> <string name="demo_mode" msgid="263484519766901593">"Demoläge för systemgränssnitt"</string> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index 8dc22a785069..9d5181e21b45 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Bonyeza kwa muda mrefu uweke mapendeleo ya wijeti"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Badilisha wijeti upendavyo"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Aikoni ya programu ya wijeti iliyozimwa"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Badilisha wijeti"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Ondoa"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Ongeza wijeti"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Badilisha wijeti upendavyo"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Wijeti kwenye skrini iliyofungwa"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"chagua wijeti"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ondoa wijeti"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"weka wijeti uliyochagua"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Badili mtumiaji"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menyu ya kuvuta chini"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Data na programu zote katika kipindi hiki zitafutwa."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Weka mipangilio ya sauti inayotoka"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Vitelezi vya sauti vimepanuliwa"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Vitelezi vya kiwango cha sauti vimekunjwa"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"zima sauti ya %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"rejesha sauti ya %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Inacheza <xliff:g id="LABEL">%s</xliff:g> kwenye"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Sauti itacheza kwenye"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Simu inaendelea"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Kirekebishi cha kiolesura cha mfumo"</string> <string name="status_bar" msgid="4357390266055077437">"Sehemu ya kuonyesha hali"</string> <string name="demo_mode" msgid="263484519766901593">"Hali ya onyesho la kirekebishi cha kiolesura cha mfumo"</string> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index ee86acac2d7e..a2f4ce5424a2 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -275,7 +275,7 @@ <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"சேமிக்கப்பட்டது"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"இணைப்பு நீக்கும்"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"செயல்படுத்தும்"</string> - <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"நாளைக்குத் தானாகவே மீண்டும் இயக்கப்படும்"</string> + <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"நாளைக்குத் தானாகவே மீண்டும் இயக்கப்படுதல்"</string> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"விரைவுப் பகிர்தல், Find My Device போன்ற அம்சங்கள் புளூடூத்தைப் பயன்படுத்துகின்றன"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"நாளை காலை புளூடூத் இயக்கப்படும்"</string> <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"ஆடியோ பகிர்வு"</string> @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"விட்ஜெட்களைப் பிரத்தியேகமாக்க நீண்ட நேரம் அழுத்துக"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"விட்ஜெட்களைப் பிரத்தியேகமாக்குங்கள்"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"முடக்கப்பட்ட விட்ஜெட்டுக்கான ஆப்ஸ் ஐகான்"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"விட்ஜெட்டைத் திருத்து"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"அகற்றும்"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"விட்ஜெட்டைச் சேர்"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"விட்ஜெட்களைப் பிரத்தியேகமாக்கும்"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"பூட்டுத் திரையில் விட்ஜெட்கள்"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"விட்ஜெட்டைத் தேர்ந்தெடுக்கும்"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"விட்ஜெட்டை அகற்றும்"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"தேர்ந்தெடுத்த விட்ஜெட்டைக் காட்சிப்படுத்தும்"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"பயனரை மாற்று"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"கீழ் இழுக்கும் மெனு"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"இந்த அமர்வின் எல்லா ஆப்ஸும் தரவும் நீக்கப்படும்."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"வெளியீட்டு அமைப்புகளுக்குச் செல்லும்"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ஒலியளவு ஸ்லைடர்கள் விரிவாக்கப்பட்டன"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ஒலியளவு ஸ்லைடர்கள் சுருக்கப்பட்டன"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s ஐ ஒலியடக்கும்"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s ஐ ஒலி இயக்கும்"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"இதில் <xliff:g id="LABEL">%s</xliff:g> பிளே ஆகிறது"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"ஆடியோ இதில் பிளே ஆகும்"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"அழைப்பில் உள்ளது"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"நிலைப் பட்டி"</string> <string name="demo_mode" msgid="263484519766901593">"சிஸ்டம் பயனர் இடைமுக டெமோ பயன்முறை"</string> diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index d8622a3d9fc1..5b703a0d96fc 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"విడ్జెట్లను అనుకూలీకరించడానికి, నొక్కి, ఉంచండి"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"విడ్జెట్లను అనుకూలంగా మార్చండి"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"డిజేబుల్ చేయబడిన విడ్జెట్ కోసం యాప్ చిహ్నం"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"విడ్జెట్ను ఎడిట్ చేయండి"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"తీసివేయండి"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"విడ్జెట్ను జోడించండి"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"విడ్జెట్లను అనుకూలంగా మార్చండి"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"లాక్ స్క్రీన్లో విడ్జెట్లు"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"విడ్జెట్ను ఎంచుకోండి"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"విడ్జెట్ను తీసివేయండి"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ఎంచుకున్న విడ్జెట్ కోసం ప్లేస్ను ఎంచుకోండి"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"వినియోగదారుని మార్చు"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"పుల్డౌన్ మెనూ"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ఈ సెషన్లోని అన్ని యాప్లు మరియు డేటా తొలగించబడతాయి."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"అవుట్పుట్ సెట్టింగ్లను ఎంటర్ చేయండి"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"వాల్యూమ్ స్లయిడర్లు విస్తరించబడ్డాయి"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"వాల్యూమ్ స్లయిడర్లు కుదించబడ్డాయి"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%sను మ్యూట్ చేయండి"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%sను అన్మ్యూట్ చేయండి"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> ప్లే అయ్యే డివైజ్"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"ఆడియో ప్లే డివైజ్"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"కాల్ ప్రోగ్రెస్లో ఉంది"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"సిస్టమ్ UI ట్యూనర్"</string> <string name="status_bar" msgid="4357390266055077437">"స్టేటస్ బార్"</string> <string name="demo_mode" msgid="263484519766901593">"సిస్టమ్ UI డెమో మోడ్"</string> diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index 9f09b9ccdfe4..73ca20004444 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"กดค้างเพื่อปรับแต่งวิดเจ็ต"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ปรับแต่งวิดเจ็ต"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"ไอคอนแอปสำหรับวิดเจ็ตที่ปิดใช้อยู่"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"แก้ไขวิดเจ็ต"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"นำออก"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"เพิ่มวิดเจ็ต"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ปรับแต่งวิดเจ็ต"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"วิดเจ็ตในหน้าจอล็อก"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"เลือกวิดเจ็ต"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"นำวิดเจ็ตออก"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"จัดวางวิดเจ็ตที่เลือก"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"สลับผู้ใช้"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"เมนูแบบเลื่อนลง"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"ระบบจะลบแอปและข้อมูลทั้งหมดในเซสชันนี้"</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"เข้าสู่การตั้งค่าเอาต์พุต"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"ขยายแถบเลื่อนระดับเสียงแล้ว"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"ยุบแถบเลื่อนระดับเสียงแล้ว"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"ปิดเสียง%s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"เปิดเสียง%s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"กำลังเล่น <xliff:g id="LABEL">%s</xliff:g> ใน"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"เสียงจะเล่นต่อใน"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"กำลังโทรติดต่อ"</string> diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index 9122627107f4..0c616fa91dc1 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Pindutin nang matagal para i-customize ang mga widget"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"I-customize ang mga widget"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Icon ng app para sa na-disable na widget"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"I-edit ang widget"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Alisin"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Magdagdag ng widget"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"I-customize ang mga widget"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Mga widget sa lock screen"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"pumili ng widget"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"alisin ang widget"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"ilagay ang napiling widget"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Magpalit ng user"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pulldown menu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ide-delete ang lahat ng app at data sa session na ito."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Pumunta sa mga setting ng output"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Na-expand ang mga slider ng volume"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Na-collapse ang mga slider ng volume"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"i-mute ang %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"i-unmute ang %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Nagpe-play ang <xliff:g id="LABEL">%s</xliff:g> sa"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"I-play ang audio sa"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Tumatawag sa"</string> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index 1f7a8e0b4ca4..6723c4226f04 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Widget\'ları özelleştirmek için uzun basın"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Widget\'ları özelleştir"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Devre dışı bırakılan widget\'ın uygulama simgesi"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Widget\'ı düzenle"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Kaldır"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Widget ekle"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Widget\'ları özelleştir"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Kilit ekranındaki widget\'lar"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"widget seçin"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"widget\'ı kaldır"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"seçilen widget\'ı yerleştir"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Kullanıcı değiştirme"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"açılır menü"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Bu oturumdaki tüm uygulamalar ve veriler silinecek."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Çıkış ayarlarını gir"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Ses seviyesi kaydırma çubukları genişletildi"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Ses seviyesi kaydırma çubukları daraltıldı"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s sesini kapatma"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s sesini açma"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> şurada çalacak:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Ses şurada çalacak:"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Şu cihaz aranıyor:"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Sistem Arayüzü Ayarlayıcısı"</string> <string name="status_bar" msgid="4357390266055077437">"Durum çubuğu"</string> <string name="demo_mode" msgid="263484519766901593">"Sistem kullanıcı arayüzü demo modu"</string> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index e9b45bf14e99..2659f9fedfee 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Утримуйте, щоб налаштувати віджети"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Налаштувати віджети"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Значок додатка для вимкненого віджета"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Редагувати віджет"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Видалити"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Додати віджет"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Налаштувати віджети"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Віджети на заблокованому екрані"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"виберіть віджет"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"видалити віджет"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"розмістити вибраний віджет"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Змінити користувача"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"спадне меню"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Усі додатки й дані з цього сеансу буде видалено."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Відкрити налаштування відтворення"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Повзунки гучності розгорнуто"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Повзунки гучності згорнуто"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"вимкнути звук %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"увімкнути звук %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Де відтворюється <xliff:g id="LABEL">%s</xliff:g>:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Де гратиме аудіо:"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Триває виклик"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Рядок стану"</string> <string name="demo_mode" msgid="263484519766901593">"Демо-режим інтерфейсу системи"</string> diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index f9fc9f0de389..2a7fda1791e5 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"ویجٹس کو حسب ضرورت بنانے کے لیے لانگ پریس کریں"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"ویجیٹس کو حسب ضرورت بنائیں"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"غیر فعال ویجیٹ کے لئے ایپ آئیکن"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"ویجیٹ میں ترمیم کریں"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"ہٹائیں"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"ویجیٹ شامل کریں"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"ویجیٹس کو حسب ضرورت بنائیں"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"مقفل اسکرین پر ویجیٹس"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"ویجیٹ منتخب کریں"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"ویجیٹ ہٹائیں"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"منتخب ویجیٹ رکھیں"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"صارف سوئچ کریں"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"پل ڈاؤن مینیو"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"اس سیشن میں موجود سبھی ایپس اور ڈیٹا کو حذف کر دیا جائے گا۔"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"آؤٹ پٹ کی ترتیبات درج کریں"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"والیوم سلائیڈرز کو پھیلا دیا گیا"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"والیوم سلائیڈرز سکیڑا گیا"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%s خاموش کریں"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s غیر خاموش کریں"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> پر چل رہی ہے"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"آڈیو اس پر چلے گی"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"کال کی جا رہی ہے"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"سسٹم UI ٹیونر"</string> <string name="status_bar" msgid="4357390266055077437">"اسٹیٹس بار"</string> <string name="demo_mode" msgid="263484519766901593">"سسٹم UI ڈیمو موڈ"</string> diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index 60baeec4ed84..0d7709c51775 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Vidjetlarni sozlash uchun bosib turing"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Vidjetlarni moslashtirish"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Faolsizlantirilgan vidjet uchun ilova belgisi"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Vidjetni tahrirlash"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Olib tashlash"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Vidjet kiritish"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Vidjetlarni moslash"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Ekran qulfidagi vidjetlar"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"vidjet tanlash"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"vidjetni olib tashlash"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"tanlangan vidjetni joylash"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Foydalanuvchini almashtirish"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"tortib tushiriladigan menyu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Ushbu seansdagi barcha ilovalar va ma’lumotlar o‘chirib tashlanadi."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Chiqarish sozlamalarini kiritish"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Tovush slayderlari yoyilgan"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Tovush slayderlari yigʻilgan"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"%sni ovozsiz qilish"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"%s ovozini chiqarish"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g>da ijro etilmoqda"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Audio ijro etiladi"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Chaqiruv yoniq"</string> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index 9656eb3cf81e..2ddcd6d32f44 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Nhấn và giữ để tuỳ chỉnh tiện ích"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Tuỳ chỉnh tiện ích"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Biểu tượng ứng dụng của tiện ích đã bị vô hiệu hoá"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Chỉnh sửa tiện ích"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Xoá"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Thêm tiện ích"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Tuỳ chỉnh tiện ích"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Các tiện ích trên màn hình khoá"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"chọn tiện ích"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"xoá tiện ích"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"đặt tiện ích đã chọn vào vị trí"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Chuyển đổi người dùng"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"trình đơn kéo xuống"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tất cả ứng dụng và dữ liệu trong phiên này sẽ bị xóa."</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Mở phần cài đặt thiết bị đầu ra"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Đã mở rộng thanh trượt âm lượng"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Đã thu gọn thanh trượt âm lượng"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"tắt tiếng %s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"bật tiếng %s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Đang phát <xliff:g id="LABEL">%s</xliff:g> trên"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Âm thanh sẽ phát trên"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Đang gọi điện"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"Bộ điều hướng giao diện người dùng hệ thống"</string> <string name="status_bar" msgid="4357390266055077437">"Thanh trạng thái"</string> <string name="demo_mode" msgid="263484519766901593">"Chế độ thử nghiệm giao diện người dùng hệ thống"</string> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index 6f1a0bd0bc7b..64e37ce4b88d 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"长按即可自定义微件"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"自定义微件"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"已停用微件的应用图标"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"修改微件"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"移除"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"添加微件"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"自定义微件"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"锁定屏幕上的微件"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"选择微件"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"移除微件"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"放置所选微件"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切换用户"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉菜单"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"此会话中的所有应用和数据都将被删除。"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"进入输出设置"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"音量滑块已展开"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"音量滑块已收起"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"静音“%s”"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"取消静音“%s”"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g>播放位置:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"音频播放位置:"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"正在通话"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"系统界面调节工具"</string> <string name="status_bar" msgid="4357390266055077437">"状态栏"</string> <string name="demo_mode" msgid="263484519766901593">"系统界面演示模式"</string> diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index 47d0692c3d84..714e479cb515 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"長按即可自訂小工具"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"自訂小工具"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"已停用小工具的應用程式圖示"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"編輯小工具"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"移除"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"新增小工具"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"自訂小工具"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"上鎖畫面上的小工具"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"揀小工具"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"移除小工具"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"放置所選小工具"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切換使用者"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉式選單"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會被刪除。"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"輸入輸出設定"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"打開咗音量滑桿"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"閂埋咗音量滑桿"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"將%s設定為靜音"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"取消%s的靜音設定"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"正在播放「<xliff:g id="LABEL">%s</xliff:g>」的裝置:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"音訊播放媒體"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"通話中"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"系統使用者介面調諧器"</string> <string name="status_bar" msgid="4357390266055077437">"狀態列"</string> <string name="demo_mode" msgid="263484519766901593">"系統使用者介面示範模式"</string> diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index b3ed7a051bc0..495a055c3e70 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"長按即可自訂小工具"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"自訂小工具"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"所停用小工具的應用程式圖示"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"編輯小工具"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"移除"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"新增小工具"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"自訂小工具"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"螢幕鎖定畫面上的小工具"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"選取小工具"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"移除小工具"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"放置所選小工具"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"切換使用者"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"下拉式選單"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"這個工作階段中的所有應用程式和資料都會刪除。"</string> @@ -629,12 +629,17 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"進入輸出設定"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"音量滑桿已展開"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"音量滑桿已收合"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"將%s設為靜音"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"將%s取消靜音"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"正在播放「<xliff:g id="LABEL">%s</xliff:g>」的裝置:"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"音訊播放位置"</string> - <!-- no translation found for media_output_title_ongoing_call (208426888064112006) --> - <skip /> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"通話中"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"系統使用者介面調整精靈"</string> <string name="status_bar" msgid="4357390266055077437">"狀態列"</string> <string name="demo_mode" msgid="263484519766901593">"系統 UI 展示模式"</string> diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index 6e9e08b63095..f5ee5a0d60b1 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -447,6 +447,8 @@ <string name="popup_on_dismiss_cta_tile_text" msgid="8292501780996070019">"Cindezela isikhathi eside ukuze wenze ngokwezifiso amawijethi"</string> <string name="button_to_configure_widgets_text" msgid="4191862850185256901">"Yenza ngokwezifiso amawijethi"</string> <string name="icon_description_for_disabled_widget" msgid="4693151565003206943">"Isithonjana se-app sewijethi evaliwe"</string> + <!-- no translation found for icon_description_for_pending_widget (8413816401868001755) --> + <skip /> <string name="edit_widget" msgid="9030848101135393954">"Hlela amawijethi"</string> <string name="button_to_remove_widget" msgid="3948204829181214098">"Susa"</string> <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"Engeza iwijethi"</string> @@ -461,10 +463,8 @@ <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Yenza ngokwezifiso amawijethi"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Amawijethi ekukhiyeni isikrini"</string> <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"khetha iwijethi"</string> - <!-- no translation found for accessibility_action_label_remove_widget (3373779447448758070) --> - <skip /> - <!-- no translation found for accessibility_action_label_place_widget (1914197458644168978) --> - <skip /> + <string name="accessibility_action_label_remove_widget" msgid="3373779447448758070">"susa iwijethi"</string> + <string name="accessibility_action_label_place_widget" msgid="1914197458644168978">"beka iwijethi ekhethiwe"</string> <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"Shintsha umsebenzisi"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"imenyu yokudonsela phansi"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Wonke ama-app nedatha kulesi sikhathi azosuswa."</string> @@ -629,8 +629,14 @@ <string name="volume_panel_enter_media_output_settings" msgid="8824244246272552669">"Faka amasethingi wokuphumayo"</string> <string name="volume_panel_expanded_sliders" msgid="1885750987768506271">"Izilayidi zevolumu zinwetshiwe"</string> <string name="volume_panel_collapsed_sliders" msgid="1413383759434791450">"Izilayidi zevolumu zigoqiwe"</string> - <string name="volume_panel_hint_mute" msgid="6962563028495243738">"thulisa i-%s"</string> - <string name="volume_panel_hint_unmute" msgid="7489063242934477382">"susa ukuthula kwe-%s"</string> + <!-- no translation found for volume_panel_hint_mute (2153922288568199077) --> + <skip /> + <!-- no translation found for volume_panel_hint_unmute (4831850937582282340) --> + <skip /> + <!-- no translation found for volume_panel_hint_muted (1124844870181285320) --> + <skip /> + <!-- no translation found for volume_panel_hint_vibrate (4136223145435914132) --> + <skip /> <string name="media_output_label_title" msgid="872824698593182505">"Idlala ku-<xliff:g id="LABEL">%s</xliff:g>"</string> <string name="media_output_title_without_playing" msgid="3825663683169305013">"Umsindo uzodlala"</string> <string name="media_output_title_ongoing_call" msgid="208426888064112006">"Ifonela kokuthi"</string> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index a1daebd7513e..517b44f455d7 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -285,6 +285,9 @@ the amount by the view is positioned above the screen before the animation starts. --> <dimen name="heads_up_appear_y_above_screen">32dp</dimen> + <!-- padding between the old and new heads up notifications for the hun cycling animation --> + <dimen name="heads_up_cycling_padding">8dp</dimen> + <!-- padding between the heads up and the statusbar --> <dimen name="heads_up_status_bar_padding">8dp</dimen> @@ -446,6 +449,8 @@ <dimen name="overlay_preview_container_margin">8dp</dimen> <dimen name="overlay_action_container_margin_horizontal">8dp</dimen> <dimen name="overlay_action_container_margin_bottom">6dp</dimen> + <!-- minimum distance to the left, right or bottom edges. --> + <dimen name="overlay_action_container_minimum_edge_spacing">12dp</dimen> <dimen name="overlay_bg_protection_height">242dp</dimen> <dimen name="overlay_action_container_corner_radius">20dp</dimen> <dimen name="overlay_action_container_padding_vertical">8dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 45bcd829336f..aecc9066b552 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -384,6 +384,8 @@ <!-- Content description for the app logo icon on biometric prompt. [CHAR LIMIT=NONE] --> <string name="biometric_dialog_logo">App logo</string> + <!-- List of packages for which we want to show overridden logo. For example, an app overrides its launcher logo, if it's in this array, biometric dialog shows the overridden logo; otherwise biometric dialog still shows the default application info icon. [CHAR LIMIT=NONE] --> + <string-array name="biometric_dialog_package_names_for_logo_with_overrides" /> <!-- Message shown when a biometric is authenticated, asking the user to confirm authentication [CHAR LIMIT=30] --> <string name="biometric_dialog_confirm">Confirm</string> <!-- Button name on BiometricPrompt shown when a biometric is detected but not authenticated. Tapping the button resumes authentication [CHAR LIMIT=30] --> @@ -1158,6 +1160,8 @@ <string name="button_to_configure_widgets_text">Customize widgets</string> <!-- Description for the App icon of disabled widget. [CHAR LIMIT=NONE] --> <string name="icon_description_for_disabled_widget">App icon for disabled widget</string> + <!-- Description for the App icon of a package that is currently being installed. [CHAR LIMIT=NONE] --> + <string name="icon_description_for_pending_widget">App icon for a widget being installed</string> <!-- Label for the button which configures widgets [CHAR LIMIT=NONE] --> <string name="edit_widget">Edit widget</string> <!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] --> @@ -1634,9 +1638,15 @@ <string name="volume_panel_collapsed_sliders">Volume sliders collapsed</string> <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to mute media [CHAR_LIMIT=NONE] --> - <string name="volume_panel_hint_mute">mute %s</string> + <string name="volume_panel_hint_mute">Mute %s</string> <!-- Hint for accessibility. A stream name is a parameter. For example: double tap to unmute media [CHAR_LIMIT=NONE] --> - <string name="volume_panel_hint_unmute">unmute %s</string> + <string name="volume_panel_hint_unmute">Unmute %s</string> + + <!-- Hint for accessibility. This is announced when the stream is muted [CHAR_LIMIT=NONE] --> + <string name="volume_panel_hint_muted">muted</string> + + <!-- Hint for accessibility. This is announced when ring mode is set to Vibrate. [CHAR_LIMIT=NONE] --> + <string name="volume_panel_hint_vibrate">vibrate</string> <!-- Title with application label for media output settings when there is media playing. [CHAR LIMIT=20] --> <string name="media_output_label_title">Playing <xliff:g id="label" example="Music Player">%s</xliff:g> on</string> diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt index 422f02f0a7c9..8979ef1aa160 100644 --- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt +++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt @@ -16,7 +16,6 @@ package com.android.systemui.biometrics import android.Manifest -import android.annotation.IntDef import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX @@ -39,14 +38,9 @@ import android.view.WindowMetrics import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityManager import com.android.internal.widget.LockPatternUtils -import java.lang.annotation.Retention -import java.lang.annotation.RetentionPolicy +import com.android.systemui.biometrics.shared.model.PromptKind object Utils { - const val CREDENTIAL_PIN = 1 - const val CREDENTIAL_PATTERN = 2 - const val CREDENTIAL_PASSWORD = 3 - /** Base set of layout flags for fingerprint overlay widgets. */ const val FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS = (WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or @@ -91,17 +85,16 @@ object Utils { (promptInfo.authenticators and Authenticators.BIOMETRIC_WEAK) != 0 @JvmStatic - @CredentialType - fun getCredentialType(utils: LockPatternUtils, userId: Int): Int = + fun getCredentialType(utils: LockPatternUtils, userId: Int): PromptKind = when (utils.getKeyguardStoredPasswordQuality(userId)) { - PASSWORD_QUALITY_SOMETHING -> CREDENTIAL_PATTERN + PASSWORD_QUALITY_SOMETHING -> PromptKind.Pattern PASSWORD_QUALITY_NUMERIC, - PASSWORD_QUALITY_NUMERIC_COMPLEX -> CREDENTIAL_PIN + PASSWORD_QUALITY_NUMERIC_COMPLEX -> PromptKind.Pin PASSWORD_QUALITY_ALPHABETIC, PASSWORD_QUALITY_ALPHANUMERIC, PASSWORD_QUALITY_COMPLEX, - PASSWORD_QUALITY_MANAGED -> CREDENTIAL_PASSWORD - else -> CREDENTIAL_PASSWORD + PASSWORD_QUALITY_MANAGED -> PromptKind.Password + else -> PromptKind.Password } @JvmStatic @@ -129,8 +122,4 @@ object Utils { return windowMetrics?.windowInsets?.getInsets(WindowInsets.Type.navigationBars()) ?: Insets.NONE } - - @Retention(RetentionPolicy.SOURCE) - @IntDef(CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD) - annotation class CredentialType } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt index a97e2dc42607..b99c51489521 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt +++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,16 +16,21 @@ package com.android.systemui.biometrics.shared.model -import com.android.systemui.biometrics.Utils - -// TODO(b/251476085): this should eventually replace Utils.CredentialType -/** Credential options for biometric prompt. Shadows [Utils.CredentialType]. */ sealed interface PromptKind { + object None : PromptKind + data class Biometric( + /** The available modalities for the authentication on the prompt. */ val activeModalities: BiometricModalities = BiometricModalities(), + // TODO(b/330908557): Use this value to decide whether to show two pane layout, instead of + // simply depending on rotations. + val showTwoPane: Boolean = false, ) : PromptKind object Pin : PromptKind object Pattern : PromptKind object Password : PromptKind + + fun isBiometric() = this is Biometric + fun isCredential() = (this is Pin) or (this is Pattern) or (this is Password) } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl index d191a3c3f28b..d5bc10a322d4 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl @@ -65,7 +65,7 @@ oneway interface IOverviewProxy { /** * Sent when some system ui state changes. */ - void onSystemUiStateChanged(int stateFlags) = 16; + void onSystemUiStateChanged(long stateFlags) = 16; /** * Sent when suggested rotation button could be shown diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index 94b6fd42f701..090033d41ffa 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -162,5 +162,10 @@ interface ISystemUiProxy { oneway void setOverrideHomeButtonLongPress(long duration, float slopMultiplier, boolean haptic) = 55; - // Next id = 56 + /** + * Notifies to toggle quick settings panel. + */ + oneway void toggleQuickSettingsPanel() = 56; + + // Next id = 57 } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 69aa90996946..b4377eae4d1f 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -22,7 +22,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; import static com.android.systemui.shared.Flags.shadeAllowBackGesture; -import android.annotation.IntDef; +import android.annotation.LongDef; import android.content.Context; import android.content.res.Resources; import android.view.ViewConfiguration; @@ -51,92 +51,94 @@ public class QuickStepContract { // Overview is disabled, either because the device is in lock task mode, or because the device // policy has disabled the feature - public static final int SYSUI_STATE_SCREEN_PINNING = 1 << 0; + public static final long SYSUI_STATE_SCREEN_PINNING = 1L << 0; // The navigation bar is hidden due to immersive mode - public static final int SYSUI_STATE_NAV_BAR_HIDDEN = 1 << 1; + public static final long SYSUI_STATE_NAV_BAR_HIDDEN = 1L << 1; // The notification panel is expanded and interactive (either locked or unlocked), and the // quick settings is not expanded - public static final int SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED = 1 << 2; + public static final long SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED = 1L << 2; // The keyguard bouncer is showing - public static final int SYSUI_STATE_BOUNCER_SHOWING = 1 << 3; + public static final long SYSUI_STATE_BOUNCER_SHOWING = 1L << 3; // The navigation bar a11y button should be shown - public static final int SYSUI_STATE_A11Y_BUTTON_CLICKABLE = 1 << 4; + public static final long SYSUI_STATE_A11Y_BUTTON_CLICKABLE = 1L << 4; // The navigation bar a11y button shortcut is available - public static final int SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE = 1 << 5; + public static final long SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE = 1L << 5; // The keyguard is showing and not occluded - public static final int SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING = 1 << 6; + public static final long SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING = 1L << 6; // The recents feature is disabled (either by SUW/SysUI/device policy) - public static final int SYSUI_STATE_OVERVIEW_DISABLED = 1 << 7; + public static final long SYSUI_STATE_OVERVIEW_DISABLED = 1L << 7; // The home feature is disabled (either by SUW/SysUI/device policy) - public static final int SYSUI_STATE_HOME_DISABLED = 1 << 8; + public static final long SYSUI_STATE_HOME_DISABLED = 1L << 8; // The keyguard is showing, but occluded - public static final int SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED = 1 << 9; + public static final long SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED = 1L << 9; // The search feature is disabled (either by SUW/SysUI/device policy) - public static final int SYSUI_STATE_SEARCH_DISABLED = 1 << 10; + public static final long SYSUI_STATE_SEARCH_DISABLED = 1L << 10; // The notification panel is expanded and interactive (either locked or unlocked), and quick // settings is expanded. - public static final int SYSUI_STATE_QUICK_SETTINGS_EXPANDED = 1 << 11; + public static final long SYSUI_STATE_QUICK_SETTINGS_EXPANDED = 1L << 11; // Winscope tracing is enabled - public static final int SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION = 1 << 12; + public static final long SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION = 1L << 12; // The Assistant gesture should be constrained. It is up to the launcher implementation to // decide how to constrain it - public static final int SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED = 1 << 13; + public static final long SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED = 1L << 13; // The bubble stack is expanded. This means that the home gesture should be ignored, since a // swipe up is an attempt to close the bubble stack, but that the back gesture should remain // enabled (since it's used to navigate back within the bubbled app, or to collapse the bubble // stack. - public static final int SYSUI_STATE_BUBBLES_EXPANDED = 1 << 14; + public static final long SYSUI_STATE_BUBBLES_EXPANDED = 1L << 14; // A SysUI dialog is showing. - public static final int SYSUI_STATE_DIALOG_SHOWING = 1 << 15; + public static final long SYSUI_STATE_DIALOG_SHOWING = 1L << 15; // The one-handed mode is active - public static final int SYSUI_STATE_ONE_HANDED_ACTIVE = 1 << 16; + public static final long SYSUI_STATE_ONE_HANDED_ACTIVE = 1L << 16; // Allow system gesture no matter the system bar(s) is visible or not - public static final int SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY = 1 << 17; + public static final long SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY = 1L << 17; // The IME is showing - public static final int SYSUI_STATE_IME_SHOWING = 1 << 18; + public static final long SYSUI_STATE_IME_SHOWING = 1L << 18; // The window magnification is overlapped with system gesture insets at the bottom. - public static final int SYSUI_STATE_MAGNIFICATION_OVERLAP = 1 << 19; + public static final long SYSUI_STATE_MAGNIFICATION_OVERLAP = 1L << 19; // ImeSwitcher is showing - public static final int SYSUI_STATE_IME_SWITCHER_SHOWING = 1 << 20; + public static final long SYSUI_STATE_IME_SWITCHER_SHOWING = 1L << 20; // Device dozing/AOD state - public static final int SYSUI_STATE_DEVICE_DOZING = 1 << 21; + public static final long SYSUI_STATE_DEVICE_DOZING = 1L << 21; // The home feature is disabled (either by SUW/SysUI/device policy) - public static final int SYSUI_STATE_BACK_DISABLED = 1 << 22; + public static final long SYSUI_STATE_BACK_DISABLED = 1L << 22; // The bubble stack is expanded AND the mange menu for bubbles is expanded on top of it. - public static final int SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED = 1 << 23; + public static final long SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED = 1L << 23; // The voice interaction session window is showing - public static final int SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 25; + public static final long SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING = 1L << 25; // Freeform windows are showing in desktop mode - public static final int SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1 << 26; + public static final long SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1L << 26; // Device dreaming state - public static final int SYSUI_STATE_DEVICE_DREAMING = 1 << 27; + public static final long SYSUI_STATE_DEVICE_DREAMING = 1L << 27; // Whether the device is currently awake (as opposed to asleep, see WakefulnessLifecycle). // Note that the device is awake on while waking up on, but not while going to sleep. - public static final int SYSUI_STATE_AWAKE = 1 << 28; + public static final long SYSUI_STATE_AWAKE = 1L << 28; // Whether the device is currently transitioning between awake/asleep indicated by // SYSUI_STATE_AWAKE. - public static final int SYSUI_STATE_WAKEFULNESS_TRANSITION = 1 << 29; + public static final long SYSUI_STATE_WAKEFULNESS_TRANSITION = 1L << 29; // The notification panel expansion fraction is > 0 - public static final int SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE = 1 << 30; + public static final long SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE = 1L << 30; // When keyguard will be dismissed but didn't start animation yet - public static final int SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY = 1 << 31; + public static final long SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY = 1L << 31; + // Physical keyboard shortcuts helper is showing + public static final long SYSUI_STATE_SHORTCUT_HELPER_SHOWING = 1L << 32; // Mask for SystemUiStateFlags to isolate SYSUI_STATE_AWAKE and // SYSUI_STATE_WAKEFULNESS_TRANSITION, to match WAKEFULNESS_* constants - public static final int SYSUI_STATE_WAKEFULNESS_MASK = + public static final long SYSUI_STATE_WAKEFULNESS_MASK = SYSUI_STATE_AWAKE | SYSUI_STATE_WAKEFULNESS_TRANSITION; // Mirroring the WakefulnessLifecycle#Wakefulness states - public static final int WAKEFULNESS_ASLEEP = 0; - public static final int WAKEFULNESS_AWAKE = SYSUI_STATE_AWAKE; - public static final int WAKEFULNESS_GOING_TO_SLEEP = SYSUI_STATE_WAKEFULNESS_TRANSITION; - public static final int WAKEFULNESS_WAKING = + public static final long WAKEFULNESS_ASLEEP = 0; + public static final long WAKEFULNESS_AWAKE = SYSUI_STATE_AWAKE; + public static final long WAKEFULNESS_GOING_TO_SLEEP = SYSUI_STATE_WAKEFULNESS_TRANSITION; + public static final long WAKEFULNESS_WAKING = SYSUI_STATE_WAKEFULNESS_TRANSITION | SYSUI_STATE_AWAKE; // Whether the back gesture is allowed (or ignored) by the Shade public static final boolean ALLOW_BACK_GESTURE_IN_SHADE = shadeAllowBackGesture(); @Retention(RetentionPolicy.SOURCE) - @IntDef({SYSUI_STATE_SCREEN_PINNING, + @LongDef({SYSUI_STATE_SCREEN_PINNING, SYSUI_STATE_NAV_BAR_HIDDEN, SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED, SYSUI_STATE_QUICK_SETTINGS_EXPANDED, @@ -167,10 +169,11 @@ public class QuickStepContract { SYSUI_STATE_WAKEFULNESS_TRANSITION, SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE, SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY, + SYSUI_STATE_SHORTCUT_HELPER_SHOWING, }) public @interface SystemUiStateFlags {} - public static String getSystemUiStateString(int flags) { + public static String getSystemUiStateString(long flags) { StringJoiner str = new StringJoiner("|"); if ((flags & SYSUI_STATE_SCREEN_PINNING) != 0) { str.add("screen_pinned"); @@ -265,6 +268,9 @@ public class QuickStepContract { if ((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY) != 0) { str.add("keygrd_going_away"); } + if ((flags & SYSUI_STATE_SHORTCUT_HELPER_SHOWING) != 0) { + str.add("shortcut_helper_showing"); + } return str.toString(); } @@ -285,13 +291,13 @@ public class QuickStepContract { * Returns whether the specified sysui state is such that the assistant gesture should be * disabled. */ - public static boolean isAssistantGestureDisabled(int sysuiStateFlags) { + public static boolean isAssistantGestureDisabled(long sysuiStateFlags) { if ((sysuiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) { sysuiStateFlags &= ~SYSUI_STATE_NAV_BAR_HIDDEN; } // Disable when in quick settings, screen pinning, immersive, the bouncer is showing, // or search is disabled - int disableFlags = SYSUI_STATE_SCREEN_PINNING + long disableFlags = SYSUI_STATE_SCREEN_PINNING | SYSUI_STATE_NAV_BAR_HIDDEN | SYSUI_STATE_BOUNCER_SHOWING | SYSUI_STATE_SEARCH_DISABLED @@ -313,7 +319,7 @@ public class QuickStepContract { * Returns whether the specified sysui state is such that the back gesture should be * disabled. */ - public static boolean isBackGestureDisabled(int sysuiStateFlags, boolean forTrackpad) { + public static boolean isBackGestureDisabled(long sysuiStateFlags, boolean forTrackpad) { // Always allow when the bouncer/global actions/voice session is showing (even on top of // the keyguard) if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0 @@ -328,9 +334,9 @@ public class QuickStepContract { return (sysuiStateFlags & getBackGestureDisabledMask(forTrackpad)) != 0; } - private static int getBackGestureDisabledMask(boolean forTrackpad) { + private static long getBackGestureDisabledMask(boolean forTrackpad) { // Disable when in immersive, or the notifications are interactive - int disableFlags = SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING; + long disableFlags = SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING; if (!forTrackpad) { disableFlags |= SYSUI_STATE_NAV_BAR_HIDDEN; } diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 460779c73cda..f33acf21e8a0 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -43,6 +43,7 @@ import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.lifecycle.repeatWhenAttached @@ -432,6 +433,7 @@ constructor( listenForDozeAmountTransition(this) listenForAnyStateToAodTransition(this) listenForAnyStateToLockscreenTransition(this) + listenForAnyStateToDozingTransition(this) } else { listenForDozeAmount(this) } @@ -578,6 +580,21 @@ constructor( } } + /** + * When keyguard is displayed due to pulsing notifications when AOD is off, + * we should make sure clock is in dozing state instead of LS state + */ + @VisibleForTesting + internal fun listenForAnyStateToDozingTransition(scope: CoroutineScope): Job { + return scope.launch { + keyguardTransitionInteractor + .transitionStepsToState(DOZING) + .filter { it.transitionState == TransitionState.FINISHED } + .collect { handleDoze(1f) } + } + } + + @VisibleForTesting internal fun listenForDozing(scope: CoroutineScope): Job { return scope.launch { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java index 177d933f2d9f..35c202437ed7 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java @@ -30,6 +30,8 @@ import android.content.Context; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.util.SparseArray; import android.view.Display; import android.view.SurfaceControl; @@ -41,6 +43,8 @@ import android.view.accessibility.IMagnificationConnection; import android.view.accessibility.IRemoteMagnificationAnimationCallback; import android.window.InputTransferToken; +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.systemui.CoreStartable; @@ -69,6 +73,9 @@ import javax.inject.Inject; public class Magnification implements CoreStartable, CommandQueue.Callbacks { private static final String TAG = "Magnification"; + @VisibleForTesting static final int DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS = 300; + private static final int MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL = 1; + private final ModeSwitchesController mModeSwitchesController; private final Context mContext; private final Handler mHandler; @@ -209,8 +216,26 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { SysUiState sysUiState, OverviewProxyService overviewProxyService, SecureSettings secureSettings, DisplayTracker displayTracker, DisplayManager displayManager, AccessibilityLogger a11yLogger) { + this(context, mainHandler.getLooper(), executor, commandQueue, + modeSwitchesController, sysUiState, overviewProxyService, secureSettings, + displayTracker, displayManager, a11yLogger); + } + + @VisibleForTesting + public Magnification(Context context, Looper looper, @Main Executor executor, + CommandQueue commandQueue, ModeSwitchesController modeSwitchesController, + SysUiState sysUiState, OverviewProxyService overviewProxyService, + SecureSettings secureSettings, DisplayTracker displayTracker, + DisplayManager displayManager, AccessibilityLogger a11yLogger) { mContext = context; - mHandler = mainHandler; + mHandler = new Handler(looper) { + @Override + public void handleMessage(@NonNull Message msg) { + if (msg.what == MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL) { + showMagnificationButtonInternal(msg.arg1, msg.arg2); + } + } + }; mExecutor = executor; mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class); mCommandQueue = commandQueue; @@ -350,6 +375,21 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { @MainThread void showMagnificationButton(int displayId, int magnificationMode) { + if (Flags.delayShowMagnificationButton()) { + if (mHandler.hasMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL)) { + return; + } + mHandler.sendMessageDelayed( + mHandler.obtainMessage( + MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL, displayId, magnificationMode), + DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS); + } else { + showMagnificationButtonInternal(displayId, magnificationMode); + } + } + + @MainThread + private void showMagnificationButtonInternal(int displayId, int magnificationMode) { // not to show mode switch button if settings panel is already showing to // prevent settings panel be covered by the button. if (isMagnificationSettingsPanelShowing(displayId)) { @@ -360,6 +400,9 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks { @MainThread void removeMagnificationButton(int displayId) { + if (Flags.delayShowMagnificationButton()) { + mHandler.removeMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL); + } mModeSwitchesController.removeButton(displayId); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayChangeEvent.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayChangeEvent.kt new file mode 100644 index 000000000000..8f071e4bc874 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayChangeEvent.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.data.model + +import java.time.LocalTime + +sealed interface NightDisplayChangeEvent { + data class OnAutoModeChanged(val autoMode: Int) : NightDisplayChangeEvent + data class OnActivatedChanged(val isActivated: Boolean) : NightDisplayChangeEvent + data class OnCustomStartTimeChanged(val startTime: LocalTime?) : NightDisplayChangeEvent + data class OnCustomEndTimeChanged(val endTime: LocalTime?) : NightDisplayChangeEvent + data class OnForceAutoModeChanged(val shouldForceAutoMode: Boolean) : NightDisplayChangeEvent + data class OnLocationEnabledChanged(val locationEnabled: Boolean) : NightDisplayChangeEvent +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayState.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayState.kt new file mode 100644 index 000000000000..196876e541b1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/NightDisplayState.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.data.model + +import java.time.LocalTime + +/** models the state of NightDisplayRepository */ +data class NightDisplayState( + val autoMode: Int = 0, + val isActivated: Boolean = true, + val startTime: LocalTime? = null, + val endTime: LocalTime? = null, + val shouldForceAutoMode: Boolean = false, + val locationEnabled: Boolean = false, +) diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt index ae9f57f1f1b0..6032f0b7b618 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityRepository.kt @@ -18,7 +18,7 @@ package com.android.systemui.accessibility.data.repository import android.view.accessibility.AccessibilityManager import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import dagger.Module import dagger.Provides import kotlinx.coroutines.channels.awaitClose @@ -29,6 +29,8 @@ import kotlinx.coroutines.flow.distinctUntilChanged interface AccessibilityRepository { /** @see [AccessibilityManager.isTouchExplorationEnabled] */ val isTouchExplorationEnabled: Flow<Boolean> + /** @see [AccessibilityManager.isEnabled] */ + val isEnabled: Flow<Boolean> companion object { operator fun invoke(a11yManager: AccessibilityManager): AccessibilityRepository = @@ -47,6 +49,15 @@ private class AccessibilityRepositoryImpl( awaitClose { manager.removeTouchExplorationStateChangeListener(listener) } } .distinctUntilChanged() + + override val isEnabled: Flow<Boolean> = + conflatedCallbackFlow { + val listener = AccessibilityManager.AccessibilityStateChangeListener(::trySend) + manager.addAccessibilityStateChangeListener(listener) + trySend(manager.isEnabled) + awaitClose { manager.removeAccessibilityStateChangeListener(listener) } + } + .distinctUntilChanged() } @Module diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/NightDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/NightDisplayRepository.kt new file mode 100644 index 000000000000..bf44fabc31c4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/NightDisplayRepository.kt @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.data.repository + +import android.hardware.display.ColorDisplayManager +import android.hardware.display.NightDisplayListener +import android.os.UserHandle +import android.provider.Settings +import com.android.systemui.accessibility.data.model.NightDisplayChangeEvent +import com.android.systemui.accessibility.data.model.NightDisplayState +import com.android.systemui.dagger.NightDisplayListenerModule +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.statusbar.policy.LocationController +import com.android.systemui.user.utils.UserScopedService +import com.android.systemui.util.kotlin.isLocationEnabledFlow +import com.android.systemui.util.settings.GlobalSettings +import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.settings.SettingsProxyExt.observerFlow +import java.time.LocalTime +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.scan +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext + +class NightDisplayRepository +@Inject +constructor( + @Background private val bgCoroutineContext: CoroutineContext, + @Application private val scope: CoroutineScope, + private val globalSettings: GlobalSettings, + private val secureSettings: SecureSettings, + private val nightDisplayListenerBuilder: NightDisplayListenerModule.Builder, + private val colorDisplayManagerUserScopedService: UserScopedService<ColorDisplayManager>, + private val locationController: LocationController, +) { + private val stateFlowUserMap = mutableMapOf<Int, Flow<NightDisplayState>>() + + fun nightDisplayState(user: UserHandle): Flow<NightDisplayState> = + stateFlowUserMap.getOrPut(user.identifier) { + return merge( + colorDisplayManagerChangeEventFlow(user), + shouldForceAutoMode(user).map { + NightDisplayChangeEvent.OnForceAutoModeChanged(it) + }, + locationController.isLocationEnabledFlow().map { + NightDisplayChangeEvent.OnLocationEnabledChanged(it) + } + ) + .scan(initialState(user)) { state, event -> + when (event) { + is NightDisplayChangeEvent.OnActivatedChanged -> + state.copy(isActivated = event.isActivated) + is NightDisplayChangeEvent.OnAutoModeChanged -> + state.copy(autoMode = event.autoMode) + is NightDisplayChangeEvent.OnCustomStartTimeChanged -> + state.copy(startTime = event.startTime) + is NightDisplayChangeEvent.OnCustomEndTimeChanged -> + state.copy(endTime = event.endTime) + is NightDisplayChangeEvent.OnForceAutoModeChanged -> + state.copy(shouldForceAutoMode = event.shouldForceAutoMode) + is NightDisplayChangeEvent.OnLocationEnabledChanged -> + state.copy(locationEnabled = event.locationEnabled) + } + } + .conflate() + .onStart { emit(initialState(user)) } + .flowOn(bgCoroutineContext) + .stateIn(scope, SharingStarted.WhileSubscribed(), NightDisplayState()) + } + + /** Track changes in night display enabled state and its auto mode */ + private fun colorDisplayManagerChangeEventFlow(user: UserHandle) = callbackFlow { + val nightDisplayListener = nightDisplayListenerBuilder.setUser(user.identifier).build() + val nightDisplayCallback = + object : NightDisplayListener.Callback { + override fun onActivated(activated: Boolean) { + trySend(NightDisplayChangeEvent.OnActivatedChanged(activated)) + } + + override fun onAutoModeChanged(autoMode: Int) { + trySend(NightDisplayChangeEvent.OnAutoModeChanged(autoMode)) + } + + override fun onCustomStartTimeChanged(startTime: LocalTime?) { + trySend(NightDisplayChangeEvent.OnCustomStartTimeChanged(startTime)) + } + + override fun onCustomEndTimeChanged(endTime: LocalTime?) { + trySend(NightDisplayChangeEvent.OnCustomEndTimeChanged(endTime)) + } + } + nightDisplayListener.setCallback(nightDisplayCallback) + awaitClose { nightDisplayListener.setCallback(null) } + } + + /** @return true when the option to force auto mode is available and a value has not been set */ + private fun shouldForceAutoMode(userHandle: UserHandle): Flow<Boolean> = + combine(isForceAutoModeAvailable, isDisplayAutoModeRawNotSet(userHandle)) { + isForceAutoModeAvailable, + isDisplayAutoModeRawNotSet, + -> + isForceAutoModeAvailable && isDisplayAutoModeRawNotSet + } + + private val isForceAutoModeAvailable: Flow<Boolean> = + globalSettings + .observerFlow(IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME) + .onStart { emit(Unit) } + .map { + globalSettings.getString(IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME) == + NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE + } + .distinctUntilChanged() + + /** Inspired by [ColorDisplayService.getNightDisplayAutoModeRawInternal] */ + private fun isDisplayAutoModeRawNotSet(userHandle: UserHandle): Flow<Boolean> = + if (userHandle.identifier == UserHandle.USER_NULL) { + flowOf(IS_AUTO_MODE_RAW_NOT_SET_DEFAULT) + } else { + secureSettings + .observerFlow(userHandle.identifier, DISPLAY_AUTO_MODE_RAW_SETTING_NAME) + .onStart { emit(Unit) } + .map { + secureSettings.getIntForUser( + DISPLAY_AUTO_MODE_RAW_SETTING_NAME, + userHandle.identifier + ) == NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET + } + } + .distinctUntilChanged() + + suspend fun setNightDisplayAutoMode(autoMode: Int, user: UserHandle) { + withContext(bgCoroutineContext) { + colorDisplayManagerUserScopedService.forUser(user).nightDisplayAutoMode = autoMode + } + } + + suspend fun setNightDisplayActivated(activated: Boolean, user: UserHandle) { + withContext(bgCoroutineContext) { + colorDisplayManagerUserScopedService.forUser(user).isNightDisplayActivated = activated + } + } + + private fun initialState(user: UserHandle): NightDisplayState { + val colorDisplayManager = colorDisplayManagerUserScopedService.forUser(user) + return NightDisplayState( + colorDisplayManager.nightDisplayAutoMode, + colorDisplayManager.isNightDisplayActivated, + colorDisplayManager.nightDisplayCustomStartTime, + colorDisplayManager.nightDisplayCustomEndTime, + globalSettings.getString(IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME) == + NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE && + secureSettings.getIntForUser(DISPLAY_AUTO_MODE_RAW_SETTING_NAME, user.identifier) == + NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET, + locationController.isLocationEnabled, + ) + } + + private companion object { + const val NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET = -1 + const val NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE = "1" + const val IS_AUTO_MODE_RAW_NOT_SET_DEFAULT = true + const val IS_FORCE_AUTO_MODE_AVAILABLE_SETTING_NAME = + Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE + const val DISPLAY_AUTO_MODE_RAW_SETTING_NAME = Settings.Secure.NIGHT_DISPLAY_AUTO_MODE + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt index 968ce0dc8cb0..93b624a310b4 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/AccessibilityInteractor.kt @@ -28,6 +28,8 @@ constructor( private val a11yRepo: AccessibilityRepository, ) { /** @see [android.view.accessibility.AccessibilityManager.isTouchExplorationEnabled] */ - val isTouchExplorationEnabled: Flow<Boolean> - get() = a11yRepo.isTouchExplorationEnabled + val isTouchExplorationEnabled: Flow<Boolean> = a11yRepo.isTouchExplorationEnabled + + /** @see [android.view.accessibility.AccessibilityManager.isEnabled] */ + val isEnabled: Flow<Boolean> = a11yRepo.isEnabled } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt index 54dd6d00aa48..ed9597ddf559 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt @@ -41,6 +41,10 @@ import com.android.systemui.qs.tiles.impl.inversion.domain.ColorInversionTileMap import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionTileDataInteractor import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionUserActionInteractor import com.android.systemui.qs.tiles.impl.inversion.domain.model.ColorInversionTileModel +import com.android.systemui.qs.tiles.impl.night.domain.interactor.NightDisplayTileDataInteractor +import com.android.systemui.qs.tiles.impl.night.domain.interactor.NightDisplayTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel +import com.android.systemui.qs.tiles.impl.night.ui.NightDisplayTileMapper import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileDataInteractor import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileUserActionInteractor import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel @@ -117,6 +121,7 @@ interface QSAccessibilityModule { const val FONT_SCALING_TILE_SPEC = "font_scaling" const val REDUCE_BRIGHTNESS_TILE_SPEC = "reduce_brightness" const val ONE_HANDED_TILE_SPEC = "onehanded" + const val NIGHT_DISPLAY_TILE_SPEC = "night" @Provides @IntoMap @@ -279,5 +284,41 @@ interface QSAccessibilityModule { mapper, ) else StubQSTileViewModel + + @Provides + @IntoMap + @StringKey(NIGHT_DISPLAY_TILE_SPEC) + fun provideNightDisplayTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = + QSTileConfig( + tileSpec = TileSpec.create(NIGHT_DISPLAY_TILE_SPEC), + uiConfig = + QSTileUIConfig.Resource( + iconRes = R.drawable.qs_nightlight_icon_off, + labelRes = R.string.quick_settings_night_display_label, + ), + instanceId = uiEventLogger.getNewInstanceId(), + ) + + /** + * Inject NightDisplay Tile into tileViewModelMap in QSModule. The tile is hidden behind a + * flag. + */ + @Provides + @IntoMap + @StringKey(NIGHT_DISPLAY_TILE_SPEC) + fun provideNightDisplayTileViewModel( + factory: QSTileViewModelFactory.Static<NightDisplayTileModel>, + mapper: NightDisplayTileMapper, + stateInteractor: NightDisplayTileDataInteractor, + userActionInteractor: NightDisplayTileUserActionInteractor + ): QSTileViewModel = + if (Flags.qsNewTilesFuture()) + factory.create( + TileSpec.create(NIGHT_DISPLAY_TILE_SPEC), + userActionInteractor, + stateInteractor, + mapper, + ) + else StubQSTileViewModel } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 5ba0b2df7664..298c0f782b79 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -73,9 +73,9 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.biometrics.AuthController.ScaleFactorProvider; -import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor; import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor; import com.android.systemui.biometrics.shared.model.BiometricModalities; +import com.android.systemui.biometrics.shared.model.PromptKind; import com.android.systemui.biometrics.ui.BiometricPromptLayout; import com.android.systemui.biometrics.ui.CredentialView; import com.android.systemui.biometrics.ui.binder.BiometricViewBinder; @@ -149,7 +149,6 @@ public class AuthContainerView extends LinearLayout private final CoroutineScope mApplicationCoroutineScope; // TODO(b/287311775): these should be migrated out once ready - private final Provider<PromptCredentialInteractor> mPromptCredentialInteractor; private final @NonNull Provider<PromptSelectorInteractor> mPromptSelectorInteractorProvider; // TODO(b/287311775): these should be migrated out of the view private final Provider<CredentialViewModel> mCredentialViewModelProvider; @@ -310,7 +309,6 @@ public class AuthContainerView extends LinearLayout @NonNull UserManager userManager, @NonNull LockPatternUtils lockPatternUtils, @NonNull InteractionJankMonitor jankMonitor, - @NonNull Provider<PromptCredentialInteractor> promptCredentialInteractor, @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractor, @NonNull PromptViewModel promptViewModel, @NonNull Provider<CredentialViewModel> credentialViewModelProvider, @@ -318,7 +316,7 @@ public class AuthContainerView extends LinearLayout @NonNull VibratorHelper vibratorHelper) { this(config, applicationCoroutineScope, fpProps, faceProps, wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils, - jankMonitor, promptSelectorInteractor, promptCredentialInteractor, promptViewModel, + jankMonitor, promptSelectorInteractor, promptViewModel, credentialViewModelProvider, new Handler(Looper.getMainLooper()), bgExecutor, vibratorHelper); } @@ -334,7 +332,6 @@ public class AuthContainerView extends LinearLayout @NonNull LockPatternUtils lockPatternUtils, @NonNull InteractionJankMonitor jankMonitor, @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractorProvider, - @NonNull Provider<PromptCredentialInteractor> credentialInteractor, @NonNull PromptViewModel promptViewModel, @NonNull Provider<CredentialViewModel> credentialViewModelProvider, @NonNull Handler mainHandler, @@ -357,13 +354,19 @@ public class AuthContainerView extends LinearLayout mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN; mBiometricCallback = new BiometricCallback(); + mFpProps = fpProps; + mFaceProps = faceProps; + final BiometricModalities biometricModalities = new BiometricModalities( + Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds), + Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds)); + mPromptSelectorInteractorProvider = promptSelectorInteractorProvider; - mPromptSelectorInteractorProvider.get().setShouldShowBpWithoutIconForCredential( - config.mPromptInfo); + mPromptSelectorInteractorProvider.get().setPrompt(mConfig.mPromptInfo, mEffectiveUserId, + biometricModalities, mConfig.mOperationId, mConfig.mOpPackageName, + false /*onSwitchToCredential*/); final LayoutInflater layoutInflater = LayoutInflater.from(mContext); - if (constraintBp() && (Utils.isBiometricAllowed(config.mPromptInfo) - || mPromptViewModel.getShowBpWithoutIconForCredential().getValue())) { + if (constraintBp() && mPromptViewModel.getPromptKind().getValue().isBiometric()) { mLayout = (ConstraintLayout) layoutInflater.inflate( R.layout.biometric_prompt_constraint_layout, this, false /* attachToRoot */); } else { @@ -397,10 +400,7 @@ public class AuthContainerView extends LinearLayout mPanelController = new AuthPanelController(mContext, mPanelView); mBackgroundExecutor = bgExecutor; mInteractionJankMonitor = jankMonitor; - mPromptCredentialInteractor = credentialInteractor; mCredentialViewModelProvider = credentialViewModelProvider; - mFpProps = fpProps; - mFaceProps = faceProps; showPrompt(config, layoutInflater, promptViewModel, Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds), @@ -429,11 +429,12 @@ public class AuthContainerView extends LinearLayout @Nullable FaceSensorPropertiesInternal faceProps, @NonNull VibratorHelper vibratorHelper ) { - if (Utils.isBiometricAllowed(config.mPromptInfo) - || mPromptViewModel.getShowBpWithoutIconForCredential().getValue()) { + if (mPromptViewModel.getPromptKind().getValue().isBiometric()) { addBiometricView(config, layoutInflater, viewModel, fpProps, faceProps, vibratorHelper); - } else if (constraintBp() && Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) { - addCredentialView(true, false); + } else if (mPromptViewModel.getPromptKind().getValue().isCredential()) { + if (constraintBp()) { + addCredentialView(true, false); + } } else { mPromptSelectorInteractorProvider.get().resetPrompt(); } @@ -444,12 +445,6 @@ public class AuthContainerView extends LinearLayout @Nullable FingerprintSensorPropertiesInternal fpProps, @Nullable FaceSensorPropertiesInternal faceProps, @NonNull VibratorHelper vibratorHelper) { - mPromptSelectorInteractorProvider.get().useBiometricsForAuthentication( - config.mPromptInfo, - config.mUserId, - config.mOperationId, - new BiometricModalities(fpProps, faceProps), - config.mOpPackageName); if (constraintBp()) { mBiometricView = BiometricViewBinder.bind(mLayout, viewModel, null, @@ -500,34 +495,24 @@ public class AuthContainerView extends LinearLayout private void addCredentialView(boolean animatePanel, boolean animateContents) { final LayoutInflater factory = LayoutInflater.from(mContext); - @Utils.CredentialType final int credentialType = Utils.getCredentialType( - mLockPatternUtils, mEffectiveUserId); - - switch (credentialType) { - case Utils.CREDENTIAL_PATTERN: - mCredentialView = factory.inflate( - R.layout.auth_credential_pattern_view, null, false); - break; - case Utils.CREDENTIAL_PIN: - mCredentialView = factory.inflate(R.layout.auth_credential_pin_view, null, false); - break; - case Utils.CREDENTIAL_PASSWORD: - mCredentialView = factory.inflate( - R.layout.auth_credential_password_view, null, false); - break; - default: - throw new IllegalStateException("Unknown credential type: " + credentialType); + PromptKind credentialType = Utils.getCredentialType(mLockPatternUtils, mEffectiveUserId); + final int layoutResourceId; + if (credentialType instanceof PromptKind.Pattern) { + layoutResourceId = R.layout.auth_credential_pattern_view; + } else if (credentialType instanceof PromptKind.Pin) { + layoutResourceId = R.layout.auth_credential_pin_view; + } else if (credentialType instanceof PromptKind.Password) { + layoutResourceId = R.layout.auth_credential_password_view; + } else { + throw new IllegalStateException("Unknown credential type: " + credentialType); } + mCredentialView = factory.inflate(layoutResourceId, null, false); // The background is used for detecting taps / cancelling authentication. Since the // credential view is full-screen and should not be canceled from background taps, // disable it. mBackgroundView.setOnClickListener(null); mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); - - mPromptSelectorInteractorProvider.get().useCredentialsForAuthentication( - mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId, - mConfig.mOpPackageName); final CredentialViewModel vm = mCredentialViewModelProvider.get(); vm.setAnimateContents(animateContents); ((CredentialView) mCredentialView).init(vm, this, mPanelController, animatePanel, @@ -562,10 +547,9 @@ public class AuthContainerView extends LinearLayout () -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED)); if (constraintBp()) { // Do nothing on attachment with constraintLayout - } else if (Utils.isBiometricAllowed(mConfig.mPromptInfo) - || mPromptViewModel.getShowBpWithoutIconForCredential().getValue()) { + } else if (mPromptViewModel.getPromptKind().getValue().isBiometric()) { mBiometricScrollView.addView(mBiometricView.asView()); - } else if (Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) { + } else if (mPromptViewModel.getPromptKind().getValue().isCredential()) { addCredentialView(true /* animatePanel */, false /* animateContents */); } else { throw new IllegalStateException("Unknown configuration: " diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index ca88d40df6af..d6d40f28d288 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -69,7 +69,6 @@ import com.android.internal.os.SomeArgs; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.CoreStartable; import com.android.systemui.biometrics.domain.interactor.LogContextInteractor; -import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor; import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor; import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams; import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel; @@ -139,7 +138,6 @@ public class AuthController implements private Job mBiometricContextListenerJob = null; // TODO: these should be migrated out once ready - @NonNull private final Provider<PromptCredentialInteractor> mPromptCredentialInteractor; @NonNull private final Provider<PromptSelectorInteractor> mPromptSelectorInteractor; @NonNull private final Provider<CredentialViewModel> mCredentialViewModelProvider; @NonNull private final Provider<PromptViewModel> mPromptViewModelProvider; @@ -735,7 +733,6 @@ public class AuthController implements @NonNull LockPatternUtils lockPatternUtils, @NonNull Lazy<UdfpsLogger> udfpsLogger, @NonNull Lazy<LogContextInteractor> logContextInteractor, - @NonNull Provider<PromptCredentialInteractor> promptCredentialInteractorProvider, @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractorProvider, @NonNull Provider<CredentialViewModel> credentialViewModelProvider, @NonNull Provider<PromptViewModel> promptViewModelProvider, @@ -768,7 +765,6 @@ public class AuthController implements mLogContextInteractor = logContextInteractor; mPromptSelectorInteractor = promptSelectorInteractorProvider; - mPromptCredentialInteractor = promptCredentialInteractorProvider; mPromptViewModelProvider = promptViewModelProvider; mCredentialViewModelProvider = credentialViewModelProvider; @@ -1253,6 +1249,8 @@ public class AuthController implements } mCurrentDialog = newDialog; + // TODO(b/339532378): We should check whether |allowBackgroundAuthentication| should be + // removed. if (!promptInfo.isAllowBackgroundAuthentication() && !isOwnerInForeground()) { cancelIfOwnerIsNotInForeground(); } else { @@ -1316,8 +1314,8 @@ public class AuthController implements config.mScaleProvider = this::getScaleFactor; return new AuthContainerView(config, mApplicationCoroutineScope, mFpProps, mFaceProps, wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils, - mInteractionJankMonitor, mPromptCredentialInteractor, mPromptSelectorInteractor, - viewModel, mCredentialViewModelProvider, bgExecutor, mVibratorHelper); + mInteractionJankMonitor, mPromptSelectorInteractor, viewModel, + mCredentialViewModelProvider, bgExecutor, mVibratorHelper); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt index 20e81c293e40..14d8caf2a5b7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt @@ -16,8 +16,10 @@ package com.android.systemui.biometrics.dagger +import android.content.Context import android.content.res.Resources import com.android.internal.R +import com.android.launcher3.icons.IconProvider import com.android.systemui.CoreStartable import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.EllipseOverlapDetectorParams @@ -111,6 +113,9 @@ interface BiometricsModule { @Provides fun providesUdfpsUtils(): UdfpsUtils = UdfpsUtils() @Provides + fun provideIconProvider(context: Context): IconProvider = IconProvider(context) + + @Provides @SysUISingleton fun providesOverlapDetector(): OverlapDetector { val selectedOption = diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt index 9ad3f4313838..58b238b820c3 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt @@ -16,12 +16,8 @@ package com.android.systemui.biometrics.data.repository -import android.hardware.biometrics.Flags import android.hardware.biometrics.PromptInfo -import com.android.systemui.Flags.constraintBp import com.android.systemui.biometrics.AuthController -import com.android.systemui.biometrics.Utils -import com.android.systemui.biometrics.Utils.isDeviceCredentialAllowed import com.android.systemui.biometrics.shared.model.PromptKind import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow @@ -56,8 +52,8 @@ interface PromptRepository { /** The gatekeeper challenge, if one is associated with this prompt. */ val challenge: StateFlow<Long?> - /** The kind of credential to use (biometric, pin, pattern, etc.). */ - val kind: StateFlow<PromptKind> + /** The kind of prompt to use (biometric, pin, pattern, etc.). */ + val promptKind: StateFlow<PromptKind> /** The package name that the prompt is called from. */ val opPackageName: StateFlow<String?> @@ -69,18 +65,6 @@ interface PromptRepository { */ val isConfirmationRequired: Flow<Boolean> - /** - * If biometric prompt without icon needs to show for displaying content prior to credential - * view. - */ - val showBpWithoutIconForCredential: StateFlow<Boolean> - - /** - * Update whether biometric prompt without icon needs to show for displaying content prior to - * credential view, which should be set before [setPrompt]. - */ - fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) - /** Update the prompt configuration, which should be set before [isShowing]. */ fun setPrompt( promptInfo: PromptInfo, @@ -125,8 +109,8 @@ constructor( private val _userId: MutableStateFlow<Int?> = MutableStateFlow(null) override val userId = _userId.asStateFlow() - private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.Biometric()) - override val kind = _kind.asStateFlow() + private val _promptKind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.None) + override val promptKind = _promptKind.asStateFlow() private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null) override val opPackageName = _opPackageName.asStateFlow() @@ -145,21 +129,6 @@ constructor( } .distinctUntilChanged() - private val _showBpWithoutIconForCredential: MutableStateFlow<Boolean> = MutableStateFlow(false) - override val showBpWithoutIconForCredential = _showBpWithoutIconForCredential.asStateFlow() - - override fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) { - val hasCredentialViewShown = kind.value !is PromptKind.Biometric - val showBpForCredential = - Flags.customBiometricPrompt() && - constraintBp() && - !Utils.isBiometricAllowed(promptInfo) && - isDeviceCredentialAllowed(promptInfo) && - promptInfo.contentView != null && - !promptInfo.isContentViewMoreOptionsButtonUsed - _showBpWithoutIconForCredential.value = showBpForCredential && !hasCredentialViewShown - } - override fun setPrompt( promptInfo: PromptInfo, userId: Int, @@ -167,7 +136,7 @@ constructor( kind: PromptKind, opPackageName: String, ) { - _kind.value = kind + _promptKind.value = kind _userId.value = userId _challenge.value = gatekeeperChallenge _promptInfo.value = promptInfo @@ -178,7 +147,7 @@ constructor( _promptInfo.value = null _userId.value = null _challenge.value = null - _kind.value = PromptKind.Biometric() + _promptKind.value = PromptKind.None _opPackageName.value = null } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt index b7c0fa802db3..14ba8a22a65e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt @@ -16,10 +16,8 @@ package com.android.systemui.biometrics.domain.interactor -import android.hardware.biometrics.PromptInfo import com.android.internal.widget.LockPatternView import com.android.internal.widget.LockscreenCredential -import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.data.repository.PromptRepository import com.android.systemui.biometrics.domain.model.BiometricOperationInfo import com.android.systemui.biometrics.domain.model.BiometricPromptRequest @@ -42,12 +40,6 @@ import kotlinx.coroutines.withContext * Business logic for BiometricPrompt's CredentialViews, which primarily includes checking a users * PIN, pattern, or password credential instead of a biometric. * - * This is used to cache the calling app's options that were given to the underlying authenticate - * APIs and should be set before any UI is shown to the user. - * - * There can be at most one request active at a given time. Use [resetPrompt] when no request is - * active to clear the cache. - * * Views that use any biometric should use [PromptSelectorInteractor] instead. */ class PromptCredentialInteractor @@ -74,13 +66,13 @@ constructor( biometricPromptRepository.promptInfo, biometricPromptRepository.challenge, biometricPromptRepository.userId, - biometricPromptRepository.kind - ) { promptInfo, challenge, userId, kind -> + biometricPromptRepository.promptKind + ) { promptInfo, challenge, userId, promptKind -> if (promptInfo == null || userId == null || challenge == null) { return@combine null } - when (kind) { + when (promptKind) { PromptKind.Pin -> BiometricPromptRequest.Credential.Pin( info = promptInfo, @@ -137,28 +129,6 @@ constructor( private val _verificationError = MutableStateFlow<CredentialStatus.Fail?>(null) val verificationError: Flow<CredentialStatus.Fail?> = _verificationError.asStateFlow() - /** Update the current request to use credential-based authentication instead of biometrics. */ - fun useCredentialsForAuthentication( - promptInfo: PromptInfo, - @Utils.CredentialType kind: Int, - userId: Int, - challenge: Long, - opPackageName: String, - ) { - biometricPromptRepository.setPrompt( - promptInfo, - userId, - challenge, - kind.asBiometricPromptCredential(), - opPackageName, - ) - } - - /** Unset the current authentication request. */ - fun resetPrompt() { - biometricPromptRepository.unsetPrompt() - } - /** * Check a credential and return the attestation token (HAT) if successful. * @@ -231,13 +201,3 @@ constructor( _verificationError.value = null } } - -// TODO(b/251476085): remove along with Utils.CredentialType -/** Convert a [Utils.CredentialType] to the corresponding [PromptKind]. */ -private fun @receiver:Utils.CredentialType Int.asBiometricPromptCredential(): PromptKind = - when (this) { - Utils.CREDENTIAL_PIN -> PromptKind.Pin - Utils.CREDENTIAL_PASSWORD -> PromptKind.Password - Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern - else -> PromptKind.Biometric() - } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt index 45816c12281e..4ba780fcec69 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.biometrics.domain.interactor +import android.hardware.biometrics.Flags import android.hardware.biometrics.PromptInfo import com.android.internal.widget.LockPatternUtils import com.android.systemui.biometrics.Utils @@ -53,12 +54,16 @@ interface PromptSelectorInteractor { /** Static metadata about the current prompt. */ val prompt: Flow<BiometricPromptRequest.Biometric?> + /** The kind of prompt to use (biometric, pin, pattern, etc.). */ + val promptKind: StateFlow<PromptKind> + /** If using a credential is allowed. */ val isCredentialAllowed: Flow<Boolean> /** - * The kind of credential the user may use as a fallback or [PromptKind.Biometric] if unknown or - * not [isCredentialAllowed]. + * The kind of credential the user may use as a fallback or [PromptKind.None] if unknown or not + * [isCredentialAllowed]. This is separate from [promptKind], even if [promptKind] is + * [PromptKind.Biometric], [credentialKind] should still be one of pin/pattern/password. */ val credentialKind: Flow<PromptKind> @@ -71,34 +76,20 @@ interface PromptSelectorInteractor { /** Fingerprint sensor type */ val sensorType: Flow<FingerprintSensorType> - /** - * If biometric prompt without icon needs to show for displaying content prior to credential - * view. - */ - val showBpWithoutIconForCredential: StateFlow<Boolean> + /** Switch to the credential view. */ + fun onSwitchToCredential() /** - * Update whether biometric prompt without icon needs to show for displaying content prior to - * credential view, which should be set before [PromptRepository.setPrompt]. + * Update the kind of prompt (biometric prompt w/ or w/o sensor icon, pin view, pattern view, + * etc). */ - fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) - - /** Use biometrics for authentication. */ - fun useBiometricsForAuthentication( + fun setPrompt( promptInfo: PromptInfo, - userId: Int, - challenge: Long, + effectiveUserId: Int, modalities: BiometricModalities, - opPackageName: String, - ) - - /** Use credential-based authentication instead of biometrics. */ - fun useCredentialsForAuthentication( - promptInfo: PromptInfo, - @Utils.CredentialType kind: Int, - userId: Int, challenge: Long, opPackageName: String, + onSwitchToCredential: Boolean, ) /** Unset the current authentication request. */ @@ -111,7 +102,7 @@ class PromptSelectorInteractorImpl constructor( fingerprintPropertyRepository: FingerprintPropertyRepository, private val promptRepository: PromptRepository, - lockPatternUtils: LockPatternUtils, + private val lockPatternUtils: LockPatternUtils, ) : PromptSelectorInteractor { override val prompt: Flow<BiometricPromptRequest.Biometric?> = @@ -119,7 +110,7 @@ constructor( promptRepository.promptInfo, promptRepository.challenge, promptRepository.userId, - promptRepository.kind, + promptRepository.promptKind, promptRepository.opPackageName, ) { promptInfo, challenge, userId, kind, opPackageName -> if ( @@ -141,6 +132,8 @@ constructor( } } + override val promptKind: StateFlow<PromptKind> = promptRepository.promptKind + override val isConfirmationRequired: Flow<Boolean> = promptRepository.isConfirmationRequired.distinctUntilChanged() @@ -152,55 +145,61 @@ constructor( override val credentialKind: Flow<PromptKind> = combine(prompt, isCredentialAllowed) { prompt, isAllowed -> if (prompt != null && isAllowed) { - when ( - getCredentialType(lockPatternUtils, prompt.userInfo.deviceCredentialOwnerId) - ) { - Utils.CREDENTIAL_PIN -> PromptKind.Pin - Utils.CREDENTIAL_PASSWORD -> PromptKind.Password - Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern - else -> PromptKind.Biometric() - } + getCredentialType(lockPatternUtils, prompt.userInfo.deviceCredentialOwnerId) } else { - PromptKind.Biometric() + PromptKind.None } } override val sensorType: Flow<FingerprintSensorType> = fingerprintPropertyRepository.sensorType - override val showBpWithoutIconForCredential = promptRepository.showBpWithoutIconForCredential - - override fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) { - promptRepository.setShouldShowBpWithoutIconForCredential(promptInfo) - } - - override fun useBiometricsForAuthentication( - promptInfo: PromptInfo, - userId: Int, - challenge: Long, - modalities: BiometricModalities, - opPackageName: String, - ) { - promptRepository.setPrompt( - promptInfo = promptInfo, - userId = userId, - gatekeeperChallenge = challenge, - kind = PromptKind.Biometric(modalities), - opPackageName = opPackageName, + override fun onSwitchToCredential() { + val modalities: BiometricModalities = + if (promptRepository.promptKind.value.isBiometric()) + (promptRepository.promptKind.value as PromptKind.Biometric).activeModalities + else BiometricModalities() + setPrompt( + promptRepository.promptInfo.value!!, + promptRepository.userId.value!!, + modalities, + promptRepository.challenge.value!!, + promptRepository.opPackageName.value!!, + true /*onSwitchToCredential*/ ) } - override fun useCredentialsForAuthentication( + override fun setPrompt( promptInfo: PromptInfo, - @Utils.CredentialType kind: Int, - userId: Int, + effectiveUserId: Int, + modalities: BiometricModalities, challenge: Long, opPackageName: String, + onSwitchToCredential: Boolean, ) { + val hasCredentialViewShown = promptKind.value.isCredential() + val showBpForCredential = + Flags.customBiometricPrompt() && + com.android.systemui.Flags.constraintBp() && + !Utils.isBiometricAllowed(promptInfo) && + isDeviceCredentialAllowed(promptInfo) && + promptInfo.contentView != null && + !promptInfo.isContentViewMoreOptionsButtonUsed + val showBpWithoutIconForCredential = showBpForCredential && !hasCredentialViewShown + var kind: PromptKind = PromptKind.None + if (onSwitchToCredential) { + kind = getCredentialType(lockPatternUtils, effectiveUserId) + } else if (Utils.isBiometricAllowed(promptInfo) || showBpWithoutIconForCredential) { + // TODO(b/330908557): check to show one pane or two pane + kind = PromptKind.Biometric(modalities) + } else if (isDeviceCredentialAllowed(promptInfo)) { + kind = getCredentialType(lockPatternUtils, effectiveUserId) + } + promptRepository.setPrompt( promptInfo = promptInfo, - userId = userId, + userId = effectiveUserId, gatekeeperChallenge = challenge, - kind = kind.asBiometricPromptCredential(), + kind = kind, opPackageName = opPackageName, ) } @@ -209,13 +208,3 @@ constructor( promptRepository.unsetPrompt() } } - -// TODO(b/251476085): remove along with Utils.CredentialType -/** Convert a [Utils.CredentialType] to the corresponding [PromptKind]. */ -private fun @receiver:Utils.CredentialType Int.asBiometricPromptCredential(): PromptKind = - when (this) { - Utils.CREDENTIAL_PIN -> PromptKind.Pin - Utils.CREDENTIAL_PASSWORD -> PromptKind.Password - Utils.CREDENTIAL_PATTERN -> PromptKind.Pattern - else -> PromptKind.Biometric() - } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt index 6f079e2d3b3a..4f96c1e03d11 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt @@ -1,5 +1,6 @@ package com.android.systemui.biometrics.domain.model +import android.content.ComponentName import android.graphics.Bitmap import android.hardware.biometrics.PromptContentView import android.hardware.biometrics.PromptInfo @@ -43,6 +44,9 @@ sealed class BiometricPromptRequest( val logoBitmap: Bitmap? = info.logoBitmap val logoDescription: String? = info.logoDescription val negativeButtonText: String = info.negativeButtonText?.toString() ?: "" + val componentNameForConfirmDeviceCredentialActivity: ComponentName? = + info.componentNameForConfirmDeviceCredentialActivity + val allowBackgroundAuthentication = info.isAllowBackgroundAuthentication } /** Prompt using a credential (pin, pattern, password). */ diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index da5695163cb4..65c5b6b3859c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -230,7 +230,7 @@ object BiometricViewBinder { } lifecycleScope.launch { - viewModel.showBpWithoutIconForCredential.collect { showWithoutIcon -> + viewModel.hideSensorIcon.collect { showWithoutIcon -> if (!showWithoutIcon) { PromptIconViewBinder.bind( iconView, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt index d1ad783c7cc8..f0969eda4029 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt @@ -55,6 +55,7 @@ import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import kotlin.math.abs import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch /** Helper for [BiometricViewBinder] to handle resize transitions. */ @@ -169,14 +170,14 @@ object BiometricViewSizeBinder { val flipConstraintSet = ConstraintSet() view.doOnLayout { - fun setVisibilities(size: PromptSize) { + fun setVisibilities(hideSensorIcon: Boolean, size: PromptSize) { viewsToHideWhenSmall.forEach { it.showContentOrHide(forceHide = size.isSmall) } largeConstraintSet.setVisibility(iconHolderView.id, View.GONE) largeConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE) largeConstraintSet.setVisibility(R.id.indicator, View.GONE) largeConstraintSet.setVisibility(R.id.scrollView, View.GONE) - if (viewModel.showBpWithoutIconForCredential.value) { + if (hideSensorIcon) { smallConstraintSet.setVisibility(iconHolderView.id, View.GONE) smallConstraintSet.setVisibility(R.id.biometric_icon_overlay, View.GONE) smallConstraintSet.setVisibility(R.id.indicator, View.GONE) @@ -362,12 +363,16 @@ object BiometricViewSizeBinder { } } } + lifecycleScope.launch { + combine(viewModel.hideSensorIcon, viewModel.size, ::Pair).collect { + (hideSensorIcon, size) -> + setVisibilities(hideSensorIcon, size) + } + } lifecycleScope.launch { combine(viewModel.position, viewModel.size, ::Pair).collect { (position, size) -> - setVisibilities(size) - if (position.isLeft) { if (size.isSmall) { flipConstraintSet.clone(smallConstraintSet) @@ -481,7 +486,7 @@ object BiometricViewSizeBinder { v.showContentOrHide(forceHide = size.isSmall) } - if (viewModel.showBpWithoutIconForCredential.value) { + if (viewModel.hideSensorIcon.first()) { iconHolderView.visibility = View.GONE } @@ -492,10 +497,6 @@ object BiometricViewSizeBinder { viewsToFadeInOnSizeChange.forEach { it.alpha = 0f } } - // TODO(b/302735104): Fix wrong height due to the delay of - // PromptContentView. addOnLayoutChangeListener() will cause crash - // when showing credential view, since |PromptIconViewModel| won't - // release the flow. // propagate size changes to legacy panel controller and animate // transitions view.doOnLayout { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index 2104f3e1fba6..156ec6b975a5 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -16,7 +16,10 @@ package com.android.systemui.biometrics.ui.viewmodel +import android.app.ActivityTaskManager +import android.content.ComponentName import android.content.Context +import android.content.pm.ActivityInfo import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.graphics.Rect @@ -26,18 +29,22 @@ import android.hardware.biometrics.BiometricFingerprintConstants import android.hardware.biometrics.BiometricPrompt import android.hardware.biometrics.Flags.customBiometricPrompt import android.hardware.biometrics.PromptContentView +import android.os.UserHandle import android.util.Log import android.util.RotationUtils import android.view.HapticFeedbackConstants import android.view.MotionEvent +import com.android.launcher3.icons.IconProvider import com.android.systemui.Flags.bpTalkback import com.android.systemui.Flags.constraintBp import com.android.systemui.biometrics.UdfpsUtils import com.android.systemui.biometrics.Utils +import com.android.systemui.biometrics.Utils.isSystem import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor +import com.android.systemui.biometrics.domain.model.BiometricPromptRequest import com.android.systemui.biometrics.shared.model.BiometricModalities import com.android.systemui.biometrics.shared.model.BiometricModality import com.android.systemui.biometrics.shared.model.DisplayRotation @@ -67,11 +74,13 @@ class PromptViewModel @Inject constructor( displayStateInteractor: DisplayStateInteractor, - promptSelectorInteractor: PromptSelectorInteractor, + private val promptSelectorInteractor: PromptSelectorInteractor, @Application private val context: Context, private val udfpsOverlayInteractor: UdfpsOverlayInteractor, private val biometricStatusInteractor: BiometricStatusInteractor, - private val udfpsUtils: UdfpsUtils + private val udfpsUtils: UdfpsUtils, + private val iconProvider: IconProvider, + private val activityTaskManager: ActivityTaskManager, ) { /** The set of modalities available for this prompt */ val modalities: Flow<BiometricModalities> = @@ -195,8 +204,11 @@ constructor( /** The kind of credential the user has. */ val credentialKind: Flow<PromptKind> = promptSelectorInteractor.credentialKind - val showBpWithoutIconForCredential: StateFlow<Boolean> = - promptSelectorInteractor.showBpWithoutIconForCredential + /** The kind of prompt to use (biometric, pin, pattern, etc.). */ + val promptKind: StateFlow<PromptKind> = promptSelectorInteractor.promptKind + + /** Whether the sensor icon on biometric prompt ui should be hidden. */ + val hideSensorIcon: Flow<Boolean> = modalities.map { it.isEmpty }.distinctUntilChanged() /** The label to use for the cancel button. */ val negativeButtonText: Flow<String> = @@ -496,14 +508,7 @@ constructor( !(customBiometricPrompt() && constraintBp()) || it == null -> null it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme) it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap) - else -> - try { - val info = context.getApplicationInfo(it.opPackageName) - context.packageManager.getApplicationIcon(info) - } catch (e: Exception) { - Log.w(TAG, "Cannot find icon for package " + it.opPackageName, e) - null - } + else -> context.getUserBadgedIcon(it, iconProvider, activityTaskManager) } } .distinctUntilChanged() @@ -514,15 +519,8 @@ constructor( .map { when { !(customBiometricPrompt() && constraintBp()) || it == null -> "" - it.logoDescription != null -> it.logoDescription - else -> - try { - val info = context.getApplicationInfo(it.opPackageName) - context.packageManager.getApplicationLabel(info).toString() - } catch (e: Exception) { - Log.w(TAG, "Cannot find name for package " + it.opPackageName, e) - "" - } + !it.logoDescription.isNullOrEmpty() -> it.logoDescription + else -> context.getUserBadgedLabel(it, activityTaskManager) } } .distinctUntilChanged() @@ -896,6 +894,7 @@ constructor( */ fun onSwitchToCredential() { _forceLargeSize.value = true + promptSelectorInteractor.onSwitchToCredential() } private fun vibrateOnSuccess() { @@ -922,15 +921,109 @@ constructor( } companion object { - private const val TAG = "PromptViewModel" + const val TAG = "PromptViewModel" } } -private fun Context.getApplicationInfo(packageName: String): ApplicationInfo = - packageManager.getApplicationInfo( - packageName, - PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER - ) +private fun Context.getUserBadgedIcon( + prompt: BiometricPromptRequest.Biometric, + iconProvider: IconProvider, + activityTaskManager: ActivityTaskManager +): Drawable? { + var icon: Drawable? = null + val componentName = prompt.getComponentNameForLogo(activityTaskManager) + if (componentName != null && shouldShowLogoWithOverrides(componentName)) { + val activityInfo = getActivityInfo(componentName) + icon = if (activityInfo == null) null else iconProvider.getIcon(activityInfo) + } + if (icon == null) { + val appInfo = prompt.getApplicationInfoForLogo(this, componentName) + if (appInfo == null) { + Log.w(PromptViewModel.TAG, "Cannot find app logo for package $opPackageName") + return null + } else { + icon = packageManager.getApplicationIcon(appInfo) + } + } + return packageManager.getUserBadgedIcon(icon, UserHandle.of(prompt.userInfo.userId)) +} + +private fun Context.getUserBadgedLabel( + prompt: BiometricPromptRequest.Biometric, + activityTaskManager: ActivityTaskManager +): String { + val componentName = prompt.getComponentNameForLogo(activityTaskManager) + val appInfo = prompt.getApplicationInfoForLogo(this, componentName) + return if (appInfo == null || packageManager.getApplicationLabel(appInfo).isNullOrEmpty()) { + Log.w(PromptViewModel.TAG, "Cannot find app logo for package $opPackageName") + "" + } else { + packageManager + .getUserBadgedLabel(packageManager.getApplicationLabel(appInfo), UserHandle.of(userId)) + .toString() + } +} + +private fun BiometricPromptRequest.Biometric.getComponentNameForLogo( + activityTaskManager: ActivityTaskManager +): ComponentName? { + val topActivity: ComponentName? = activityTaskManager.getTasks(1).firstOrNull()?.topActivity + return when { + componentNameForConfirmDeviceCredentialActivity != null -> + componentNameForConfirmDeviceCredentialActivity + topActivity?.packageName.contentEquals(opPackageName) -> topActivity + else -> { + Log.w(PromptViewModel.TAG, "Top activity $topActivity is not the client $opPackageName") + null + } + } +} + +private fun BiometricPromptRequest.Biometric.getApplicationInfoForLogo( + context: Context, + componentNameForLogo: ComponentName? +): ApplicationInfo? { + val packageName = + when { + componentNameForLogo != null -> componentNameForLogo.packageName + // TODO(b/339532378): We should check whether |allowBackgroundAuthentication| should be + // removed. + // This is being consistent with the check in [AuthController.showDialog()]. + allowBackgroundAuthentication || isSystem(context, opPackageName) -> opPackageName + else -> null + } + return if (packageName == null) { + Log.w(PromptViewModel.TAG, "Cannot find application info for $opPackageName") + null + } else { + context.getApplicationInfo(packageName) + } +} + +private fun Context.shouldShowLogoWithOverrides(componentName: ComponentName): Boolean { + return resources + .getStringArray(R.array.biometric_dialog_package_names_for_logo_with_overrides) + .find { componentName.packageName.contentEquals(it) } != null +} + +private fun Context.getActivityInfo(componentName: ComponentName): ActivityInfo? = + try { + packageManager.getActivityInfo(componentName, 0) + } catch (e: PackageManager.NameNotFoundException) { + Log.w(PromptViewModel.TAG, "Cannot find activity info for $opPackageName", e) + null + } + +private fun Context.getApplicationInfo(packageName: String): ApplicationInfo? = + try { + packageManager.getApplicationInfo( + packageName, + PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER + ) + } catch (e: PackageManager.NameNotFoundException) { + Log.w(PromptViewModel.TAG, "Cannot find application info for $opPackageName", e) + null + } /** How the fingerprint sensor was started for the prompt. */ enum class FingerprintStartMode { diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt index dd8c0df387dc..f0230beaa967 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt @@ -204,11 +204,7 @@ internal constructor( isEnabled: Boolean, @StringRes infoResId: Int ) { - getAutoOnToggle(dialog).apply { - isChecked = isEnabled - setEnabled(true) - alpha = ENABLED_ALPHA - } + getAutoOnToggle(dialog).isChecked = isEnabled getAutoOnToggleInfoTextView(dialog).text = dialog.context.getString(infoResId) } @@ -236,12 +232,8 @@ internal constructor( } getAutoOnToggleView(dialog).visibility = initialUiProperties.autoOnToggleVisibility - getAutoOnToggle(dialog).setOnCheckedChangeListener { view, isChecked -> + getAutoOnToggle(dialog).setOnCheckedChangeListener { _, isChecked -> mutableBluetoothAutoOnToggle.value = isChecked - view.apply { - isEnabled = false - alpha = DISABLED_ALPHA - } uiEventLogger.log(BluetoothTileDialogUiEvent.BLUETOOTH_AUTO_ON_TOGGLE_CLICKED) } } diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt index 5c64dc645283..1c1642905d7d 100644 --- a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt @@ -18,6 +18,7 @@ package com.android.systemui.common.data.repository import android.os.UserHandle import com.android.systemui.common.shared.model.PackageChangeModel +import com.android.systemui.common.shared.model.PackageInstallSession import kotlinx.coroutines.flow.Flow interface PackageChangeRepository { @@ -28,4 +29,7 @@ interface PackageChangeRepository { * [UserHandle.USER_ALL] may be used to listen to all users. */ fun packageChanged(user: UserHandle): Flow<PackageChangeModel> + + /** Emits a list of all known install sessions associated with the primary user. */ + val packageInstallSessionsForPrimaryUser: Flow<List<PackageInstallSession>> } diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt index 712a3527b7c9..41b03f1f3de6 100644 --- a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt @@ -18,6 +18,7 @@ package com.android.systemui.common.data.repository import android.os.UserHandle import com.android.systemui.common.shared.model.PackageChangeModel +import com.android.systemui.common.shared.model.PackageInstallSession import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -27,6 +28,7 @@ import kotlinx.coroutines.flow.filter class PackageChangeRepositoryImpl @Inject constructor( + packageInstallerMonitor: PackageInstallerMonitor, private val monitorFactory: PackageUpdateMonitor.Factory, ) : PackageChangeRepository { /** @@ -37,4 +39,7 @@ constructor( override fun packageChanged(user: UserHandle): Flow<PackageChangeModel> = monitor.packageChanged.filter { user == UserHandle.ALL || user == it.user } + + override val packageInstallSessionsForPrimaryUser: Flow<List<PackageInstallSession>> = + packageInstallerMonitor.installSessionsForPrimaryUser } diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt new file mode 100644 index 000000000000..46db34618c70 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageInstallerMonitor.kt @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.common.data.repository + +import android.content.pm.PackageInstaller +import android.os.Handler +import com.android.internal.annotations.GuardedBy +import com.android.systemui.common.shared.model.PackageInstallSession +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.Logger +import com.android.systemui.log.dagger.PackageChangeRepoLog +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.dropWhile +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach + +/** Monitors package install sessions for all users. */ +@SysUISingleton +class PackageInstallerMonitor +@Inject +constructor( + @Background private val bgHandler: Handler, + @Background private val bgScope: CoroutineScope, + @PackageChangeRepoLog logBuffer: LogBuffer, + private val packageInstaller: PackageInstaller, +) : PackageInstaller.SessionCallback() { + + private val logger = Logger(logBuffer, TAG) + + @GuardedBy("sessions") private val sessions = mutableMapOf<Int, PackageInstallSession>() + + private val _installSessions = + MutableStateFlow<List<PackageInstallSession>>(emptyList()).apply { + subscriptionCount + .map { count -> count > 0 } + .distinctUntilChanged() + // Drop initial false value + .dropWhile { !it } + .onEach { isActive -> + if (isActive) { + synchronized(sessions) { + sessions.putAll( + packageInstaller.allSessions + .map { session -> session.toModel() } + .associateBy { it.sessionId } + ) + updateInstallerSessionsFlow() + } + packageInstaller.registerSessionCallback( + this@PackageInstallerMonitor, + bgHandler + ) + } else { + synchronized(sessions) { + sessions.clear() + updateInstallerSessionsFlow() + } + packageInstaller.unregisterSessionCallback(this@PackageInstallerMonitor) + } + } + .launchIn(bgScope) + } + + val installSessionsForPrimaryUser: Flow<List<PackageInstallSession>> = + _installSessions.asStateFlow() + + /** Called when a new installer session is created. */ + override fun onCreated(sessionId: Int) { + logger.i({ "session created $int1" }) { int1 = sessionId } + updateSession(sessionId) + } + + /** Called when new installer session has finished. */ + override fun onFinished(sessionId: Int, success: Boolean) { + logger.i({ "session finished $int1" }) { int1 = sessionId } + synchronized(sessions) { + sessions.remove(sessionId) + updateInstallerSessionsFlow() + } + } + + /** + * Badging details for the session changed. For example, the app icon or label has been updated. + */ + override fun onBadgingChanged(sessionId: Int) { + logger.i({ "session badging changed $int1" }) { int1 = sessionId } + updateSession(sessionId) + } + + /** + * A session is considered active when there is ongoing forward progress being made. For + * example, a package started downloading. + */ + override fun onActiveChanged(sessionId: Int, active: Boolean) { + // Active status updates are not tracked for now + } + + override fun onProgressChanged(sessionId: Int, progress: Float) { + // Progress updates are not tracked for now + } + + private fun updateSession(sessionId: Int) { + val session = packageInstaller.getSessionInfo(sessionId) + + synchronized(sessions) { + if (session == null) { + sessions.remove(sessionId) + } else { + sessions[sessionId] = session.toModel() + } + updateInstallerSessionsFlow() + } + } + + @GuardedBy("sessions") + private fun updateInstallerSessionsFlow() { + _installSessions.value = sessions.values.toList() + } + + companion object { + const val TAG = "PackageInstallerMonitor" + + private fun PackageInstaller.SessionInfo.toModel(): PackageInstallSession { + return PackageInstallSession( + sessionId = this.sessionId, + packageName = this.appPackageName, + icon = this.getAppIcon(), + user = this.user, + ) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/PackageInstallSession.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/PackageInstallSession.kt new file mode 100644 index 000000000000..7025229743b9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/PackageInstallSession.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.common.shared.model + +import android.graphics.Bitmap +import android.os.UserHandle + +/** Represents a session of a package being installed on device. */ +data class PackageInstallSession( + val sessionId: Int, + val packageName: String, + val icon: Bitmap?, + val user: UserHandle, +) diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt index 9e7fb4e73a29..153b7aa3e522 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt @@ -20,6 +20,8 @@ import android.annotation.SuppressLint import android.app.DreamManager import com.android.systemui.CoreStartable import com.android.systemui.Flags.communalHub +import com.android.systemui.Flags.restartDreamOnUnocclude +import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -27,8 +29,10 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.util.kotlin.Utils.Companion.sample +import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -43,6 +47,7 @@ constructor( private val powerInteractor: PowerInteractor, private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, + private val communalInteractor: CommunalInteractor, private val dreamManager: DreamManager, @Background private val bgScope: CoroutineScope, ) : CoreStartable { @@ -52,6 +57,19 @@ constructor( return } + // Return to dream from occluded when not already dreaming. + if (restartDreamOnUnocclude()) { + keyguardTransitionInteractor.startedKeyguardTransitionStep + .sample(keyguardInteractor.isDreaming, ::Pair) + .filter { + it.first.from == KeyguardState.OCCLUDED && + it.first.to == KeyguardState.DREAMING && + !it.second + } + .onEach { dreamManager.startDream() } + .launchIn(bgScope) + } + // Restart the dream underneath the hub in order to support the ability to swipe // away the hub to enter the dream. keyguardTransitionInteractor.finishedKeyguardState diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt index e2fed6d0ea20..e5a0e5070b94 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt @@ -53,7 +53,7 @@ constructor( updateMediaModel(data) } - override fun onMediaDataRemoved(key: String) { + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { updateMediaModel() } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt index 40d744015498..b27fcfc2f3a7 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt @@ -126,7 +126,7 @@ constructor( private fun observeCtaDismissState(user: UserInfo): Flow<Boolean> = getSharedPrefsForUser(user) - .observe(CTA_DISMISSED_STATE) + .observe() // Emit at the start of collection to ensure we get an initial value .onStart { emit(Unit) } .map { getCtaDismissedState() } diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt index 1f54e70fa21b..fdb797d5ba06 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt @@ -17,14 +17,13 @@ package com.android.systemui.communal.data.repository import android.app.backup.BackupManager -import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProviderInfo import android.content.ComponentName import android.os.UserHandle -import androidx.annotation.WorkerThread +import com.android.systemui.common.data.repository.PackageChangeRepository +import com.android.systemui.common.shared.model.PackageInstallSession import com.android.systemui.communal.data.backup.CommunalBackupUtils -import com.android.systemui.communal.data.db.CommunalItemRank import com.android.systemui.communal.data.db.CommunalWidgetDao -import com.android.systemui.communal.data.db.CommunalWidgetItem import com.android.systemui.communal.nano.CommunalHubState import com.android.systemui.communal.proto.toCommunalHubState import com.android.systemui.communal.shared.model.CommunalWidgetContentModel @@ -36,13 +35,15 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog -import com.android.systemui.util.kotlin.getValue -import java.util.Optional import javax.inject.Inject import kotlin.coroutines.cancellation.CancellationException import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -88,7 +89,6 @@ interface CommunalWidgetRepository { class CommunalWidgetRepositoryImpl @Inject constructor( - appWidgetManagerOptional: Optional<AppWidgetManager>, private val appWidgetHost: CommunalAppWidgetHost, @Background private val bgScope: CoroutineScope, @Background private val bgDispatcher: CoroutineDispatcher, @@ -97,6 +97,7 @@ constructor( @CommunalLog logBuffer: LogBuffer, private val backupManager: BackupManager, private val backupUtils: CommunalBackupUtils, + packageChangeRepository: PackageChangeRepository, ) : CommunalWidgetRepository { companion object { const val TAG = "CommunalWidgetRepository" @@ -104,12 +105,39 @@ constructor( private val logger = Logger(logBuffer, TAG) - private val appWidgetManager by appWidgetManagerOptional + /** Widget metadata from database + matching [AppWidgetProviderInfo] if any. */ + private val widgetEntries: Flow<List<CommunalWidgetEntry>> = + combine( + communalWidgetDao.getWidgets(), + communalWidgetHost.appWidgetProviders, + ) { entries, providers -> + entries.mapNotNull { (rank, widget) -> + CommunalWidgetEntry( + appWidgetId = widget.widgetId, + componentName = widget.componentName, + priority = rank.rank, + providerInfo = providers[widget.widgetId] + ) + } + } + @OptIn(ExperimentalCoroutinesApi::class) override val communalWidgets: Flow<List<CommunalWidgetContentModel>> = - communalWidgetDao - .getWidgets() - .map { it.mapNotNull(::mapToContentModel) } + widgetEntries + .flatMapLatest { widgetEntries -> + // If and only if any widget is missing provider info, combine with the package + // installer sessions flow to check whether they are pending installation. This can + // happen after widgets are freshly restored from a backup. In most cases, provider + // info is available to all widgets, and is unnecessary to involve an API call to + // the package installer. + if (widgetEntries.any { it.providerInfo == null }) { + packageChangeRepository.packageInstallSessionsForPrimaryUser.map { sessions -> + widgetEntries.mapNotNull { entry -> mapToContentModel(entry, sessions) } + } + } else { + flowOf(widgetEntries.map(::mapToContentModel)) + } + } // As this reads from a database and triggers IPCs to AppWidgetManager, // it should be executed in the background. .flowOn(bgDispatcher) @@ -245,6 +273,9 @@ constructor( } appWidgetHost.deleteAppWidgetId(widgetId) } + + // Providers may have changed + communalWidgetHost.refreshProviders() } } @@ -255,16 +286,57 @@ constructor( } } - @WorkerThread + /** + * Maps a [CommunalWidgetEntry] to a [CommunalWidgetContentModel] with the assumption that the + * [AppWidgetProviderInfo] of the entry is available. + */ + private fun mapToContentModel(entry: CommunalWidgetEntry): CommunalWidgetContentModel { + return CommunalWidgetContentModel.Available( + appWidgetId = entry.appWidgetId, + providerInfo = entry.providerInfo!!, + priority = entry.priority, + ) + } + + /** + * Maps a [CommunalWidgetEntry] to a [CommunalWidgetContentModel] with a list of install + * sessions. If the [AppWidgetProviderInfo] of the entry is absent, and its package is in the + * install sessions, the entry is mapped to a pending widget. + */ private fun mapToContentModel( - entry: Map.Entry<CommunalItemRank, CommunalWidgetItem> + entry: CommunalWidgetEntry, + installSessions: List<PackageInstallSession>, ): CommunalWidgetContentModel? { - val (_, widgetId) = entry.value - val providerInfo = appWidgetManager?.getAppWidgetInfo(widgetId) ?: return null - return CommunalWidgetContentModel( - appWidgetId = widgetId, - providerInfo = providerInfo, - priority = entry.key.rank, - ) + if (entry.providerInfo != null) { + return CommunalWidgetContentModel.Available( + appWidgetId = entry.appWidgetId, + providerInfo = entry.providerInfo!!, + priority = entry.priority, + ) + } + + val session = + installSessions.firstOrNull { + it.packageName == + ComponentName.unflattenFromString(entry.componentName)?.packageName + } + return if (session != null) { + CommunalWidgetContentModel.Pending( + appWidgetId = entry.appWidgetId, + priority = entry.priority, + packageName = session.packageName, + icon = session.icon, + user = session.user, + ) + } else { + null + } } + + private data class CommunalWidgetEntry( + val appWidgetId: Int, + val componentName: String, + val priority: Int, + var providerInfo: AppWidgetProviderInfo? = null, + ) } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 5091a99b2a64..06c83962df6b 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -163,6 +163,13 @@ constructor( initialValue = false, ) + /** Whether to start dreaming when returning from occluded */ + val dreamFromOccluded: Flow<Boolean> = + keyguardTransitionInteractor + .transitionStepsToState(KeyguardState.OCCLUDED) + .map { it.from == KeyguardState.DREAMING } + .stateIn(scope = applicationScope, SharingStarted.Eagerly, false) + /** * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene]. * @@ -403,19 +410,30 @@ constructor( updateOnWorkProfileBroadcastReceived, ) { widgets, allowedCategories, _ -> widgets.map { widget -> - if (widget.providerInfo.widgetCategory and allowedCategories != 0) { - // At least one category this widget specified is allowed, so show it - WidgetContent.Widget( - appWidgetId = widget.appWidgetId, - providerInfo = widget.providerInfo, - appWidgetHost = appWidgetHost, - inQuietMode = isQuietModeEnabled(widget.providerInfo.profile) - ) - } else { - WidgetContent.DisabledWidget( - appWidgetId = widget.appWidgetId, - providerInfo = widget.providerInfo, - ) + when (widget) { + is CommunalWidgetContentModel.Available -> { + if (widget.providerInfo.widgetCategory and allowedCategories != 0) { + // At least one category this widget specified is allowed, so show it + WidgetContent.Widget( + appWidgetId = widget.appWidgetId, + providerInfo = widget.providerInfo, + appWidgetHost = appWidgetHost, + inQuietMode = isQuietModeEnabled(widget.providerInfo.profile) + ) + } else { + WidgetContent.DisabledWidget( + appWidgetId = widget.appWidgetId, + providerInfo = widget.providerInfo, + ) + } + } + is CommunalWidgetContentModel.Pending -> { + WidgetContent.PendingWidget( + appWidgetId = widget.appWidgetId, + packageName = widget.packageName, + icon = widget.icon, + ) + } } } } @@ -430,7 +448,15 @@ constructor( } else { // Get associated work profile for the currently selected user. val workProfile = userTracker.userProfiles.find { it.isManagedProfile } - list.filter { it.providerInfo.profile.identifier != workProfile?.id } + list.filter { model -> + val uid = + when (model) { + is CommunalWidgetContentModel.Available -> + model.providerInfo.profile.identifier + is CommunalWidgetContentModel.Pending -> model.user.identifier + } + uid != workProfile?.id + } } /** A flow of available smartspace targets. Currently only showing timers. */ @@ -513,7 +539,11 @@ constructor( ): List<CommunalWidgetContentModel> { val currentUserIds = userTracker.userProfiles.map { it.id }.toSet() return list.filter { widget -> - currentUserIds.contains(widget.providerInfo.profile?.identifier) + when (widget) { + is CommunalWidgetContentModel.Available -> + currentUserIds.contains(widget.providerInfo.profile?.identifier) + is CommunalWidgetContentModel.Pending -> true + } } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt index 706122789563..122240daed52 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.communal.domain.model import android.appwidget.AppWidgetProviderInfo import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE import android.content.pm.ApplicationInfo +import android.graphics.Bitmap import android.widget.RemoteViews import com.android.systemui.communal.shared.model.CommunalContentSize import com.android.systemui.communal.widgets.CommunalAppWidgetHost @@ -45,11 +46,10 @@ sealed interface CommunalContentModel { sealed interface WidgetContent : CommunalContentModel { val appWidgetId: Int - val providerInfo: AppWidgetProviderInfo data class Widget( override val appWidgetId: Int, - override val providerInfo: AppWidgetProviderInfo, + val providerInfo: AppWidgetProviderInfo, val appWidgetHost: CommunalAppWidgetHost, val inQuietMode: Boolean, ) : WidgetContent { @@ -66,7 +66,7 @@ sealed interface CommunalContentModel { data class DisabledWidget( override val appWidgetId: Int, - override val providerInfo: AppWidgetProviderInfo + val providerInfo: AppWidgetProviderInfo ) : WidgetContent { override val key = KEY.disabledWidget(appWidgetId) // Widget size is always half. @@ -75,6 +75,16 @@ sealed interface CommunalContentModel { val appInfo: ApplicationInfo? get() = providerInfo.providerInfo?.applicationInfo } + + data class PendingWidget( + override val appWidgetId: Int, + val packageName: String, + val icon: Bitmap? = null, + ) : WidgetContent { + override val key = KEY.pendingWidget(appWidgetId) + // Widget size is always half. + override val size = CommunalContentSize.HALF + } } /** A placeholder item representing a new widget being added */ @@ -127,6 +137,10 @@ sealed interface CommunalContentModel { return "disabled_widget_$id" } + fun pendingWidget(id: Int): String { + return "pending_widget_$id" + } + fun widgetPlaceholder(): String { return "widget_placeholder_${UUID.randomUUID()}" } diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt index e141dc40477c..53aecc199c4b 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt @@ -17,10 +17,27 @@ package com.android.systemui.communal.shared.model import android.appwidget.AppWidgetProviderInfo +import android.graphics.Bitmap +import android.os.UserHandle /** Encapsulates data for a communal widget. */ -data class CommunalWidgetContentModel( - val appWidgetId: Int, - val providerInfo: AppWidgetProviderInfo, - val priority: Int, -) +sealed interface CommunalWidgetContentModel { + val appWidgetId: Int + val priority: Int + + /** Widget is ready to display */ + data class Available( + override val appWidgetId: Int, + val providerInfo: AppWidgetProviderInfo, + override val priority: Int, + ) : CommunalWidgetContentModel + + /** Widget is pending installation */ + data class Pending( + override val appWidgetId: Int, + override val priority: Int, + val packageName: String, + val icon: Bitmap?, + val user: UserHandle, + ) : CommunalWidgetContentModel +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt index 3f92223fb57b..f6122ad48300 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt @@ -104,7 +104,12 @@ constructor( ): Boolean = withContext(backgroundDispatcher) { val widgets = communalInteractor.widgetContent.first() - val excludeList = widgets.mapTo(ArrayList()) { it.providerInfo } + val excludeList = + widgets.filterIsInstance<CommunalContentModel.WidgetContent.Widget>().mapTo( + ArrayList() + ) { + it.providerInfo + } getWidgetPickerActivityIntent(resources, packageManager, excludeList)?.let { try { activityLauncher.launch(it) diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt index 337d8735ea53..9114aabae2e9 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt @@ -33,6 +33,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge /** View model for transitions related to the communal hub. */ @@ -49,6 +50,27 @@ constructor( communalInteractor: CommunalInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, ) { + // Show UMO on glanceable hub immediately on transition into glanceable hub + private val showUmoFromOccludedToGlanceableHub: Flow<Boolean> = + keyguardTransitionInteractor + .transitionStepsFromState(KeyguardState.OCCLUDED) + .filter { + it.to == KeyguardState.GLANCEABLE_HUB && + (it.transitionState == TransitionState.STARTED || + it.transitionState == TransitionState.CANCELED) + } + .map { it.transitionState == TransitionState.STARTED } + + private val showUmoFromGlanceableHubToOccluded: Flow<Boolean> = + keyguardTransitionInteractor + .transitionStepsFromState(KeyguardState.GLANCEABLE_HUB) + .filter { + it.to == KeyguardState.OCCLUDED && + (it.transitionState == TransitionState.FINISHED || + it.transitionState == TransitionState.CANCELED) + } + .map { it.transitionState != TransitionState.FINISHED } + /** * Whether UMO location should be on communal. This flow is responsive to transitions so that a * new value is emitted at the right step of a transition to/from communal hub that the location @@ -60,6 +82,8 @@ constructor( glanceableHubToLockscreenTransitionViewModel.showUmo, dreamToGlanceableHubTransitionViewModel.showUmo, glanceableHubToDreamTransitionViewModel.showUmo, + showUmoFromOccludedToGlanceableHub, + showUmoFromGlanceableHubToOccluded, ) .distinctUntilChanged() 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 1120466c7acc..656e5cbafa97 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 @@ -25,7 +25,6 @@ import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.log.LogBuffer @@ -67,7 +66,6 @@ constructor( private val communalInteractor: CommunalInteractor, tutorialInteractor: CommunalTutorialInteractor, private val shadeInteractor: ShadeInteractor, - deviceEntryInteractor: DeviceEntryInteractor, @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost, @CommunalLog logBuffer: LogBuffer, ) : BaseCommunalViewModel(communalInteractor, mediaHost) { @@ -142,8 +140,6 @@ constructor( val isEnableWorkProfileDialogShowing: Flow<Boolean> = _isEnableWorkProfileDialogShowing.asStateFlow() - val deviceUnlocked: Flow<Boolean> = deviceEntryInteractor.isUnlocked - init { // Initialize our media host for the UMO. This only needs to happen once and must be done // before the MediaHierarchyManager attempts to move the UMO to the hub. diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt index 5f1d89e079a7..b7e8205e6582 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt @@ -24,6 +24,7 @@ import android.os.Looper import android.widget.RemoteViews import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger +import javax.annotation.concurrent.GuardedBy import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow @@ -47,6 +48,8 @@ class CommunalAppWidgetHost( /** App widget ids that have been removed and no longer available. */ val appWidgetIdToRemove: SharedFlow<Int> = _appWidgetIdToRemove.asSharedFlow() + @GuardedBy("observers") private val observers = mutableSetOf<Observer>() + override fun onCreateView( context: Context, appWidgetId: Int, @@ -77,6 +80,61 @@ class CommunalAppWidgetHost( } } + override fun allocateAppWidgetId(): Int { + return super.allocateAppWidgetId().also { appWidgetId -> + backgroundScope.launch { + observers.forEach { observer -> observer.onAllocateAppWidgetId(appWidgetId) } + } + } + } + + override fun deleteAppWidgetId(appWidgetId: Int) { + super.deleteAppWidgetId(appWidgetId) + backgroundScope.launch { + observers.forEach { observer -> observer.onDeleteAppWidgetId(appWidgetId) } + } + } + + override fun startListening() { + super.startListening() + backgroundScope.launch { observers.forEach { observer -> observer.onHostStartListening() } } + } + + override fun stopListening() { + super.stopListening() + backgroundScope.launch { observers.forEach { observer -> observer.onHostStopListening() } } + } + + fun addObserver(observer: Observer) { + synchronized(observers) { observers.add(observer) } + } + + fun removeObserver(observer: Observer) { + synchronized(observers) { observers.remove(observer) } + } + + /** + * Allows another class to observe the [CommunalAppWidgetHost] and handle any logic there. + * + * This is mainly for testability as it is difficult to test a real instance of [AppWidgetHost] + * which communicates with framework services. + * + * Note: all the callbacks are launched from the background scope. + */ + interface Observer { + /** Called immediately after the host has started listening for widget updates. */ + fun onHostStartListening() {} + + /** Called immediately after the host has stopped listening for widget updates. */ + fun onHostStopListening() {} + + /** Called immediately after a new app widget id has been allocated. */ + fun onAllocateAppWidgetId(appWidgetId: Int) {} + + /** Called immediately after an app widget id is to be deleted. */ + fun onDeleteAppWidgetId(appWidgetId: Int) {} + } + companion object { private const val TAG = "CommunalAppWidgetHost" } diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt index 2ccab072d57f..301da51c8082 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt @@ -39,6 +39,7 @@ class CommunalAppWidgetHostStartable @Inject constructor( private val appWidgetHost: CommunalAppWidgetHost, + private val communalWidgetHost: CommunalWidgetHost, private val communalInteractor: CommunalInteractor, private val userTracker: UserTracker, @Background private val bgScope: CoroutineScope, @@ -70,9 +71,11 @@ constructor( // Always ensure this is called on the main/ui thread. withContext(uiDispatcher) { if (active) { + communalWidgetHost.startObservingHost() appWidgetHost.startListening() } else { appWidgetHost.stopListening() + communalWidgetHost.stopObservingHost() } } @@ -83,7 +86,15 @@ constructor( private fun validateWidgetsAndDeleteOrphaned(widgets: List<CommunalWidgetContentModel>) { val currentUserIds = userTracker.userProfiles.map { it.id }.toSet() widgets - .filter { widget -> !currentUserIds.contains(widget.providerInfo.profile?.identifier) } + .filter { widget -> + val uid = + when (widget) { + is CommunalWidgetContentModel.Available -> + widget.providerInfo.profile?.identifier + is CommunalWidgetContentModel.Pending -> widget.user.identifier + } + !currentUserIds.contains(uid) + } .onEach { widget -> communalInteractor.deleteWidget(id = widget.appWidgetId) } } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt index 93e2b37cfe87..42107c1e9769 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt @@ -16,6 +16,7 @@ package com.android.systemui.communal.widgets +import android.appwidget.AppWidgetHost.AppWidgetHostListener import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProviderInfo import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL @@ -23,6 +24,9 @@ import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE import android.content.ComponentName import android.os.Bundle import android.os.UserHandle +import android.widget.RemoteViews +import androidx.annotation.WorkerThread +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog @@ -30,6 +34,11 @@ import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.kotlin.getOrNull import java.util.Optional import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch /** * Widget host that interacts with AppWidget service and host to bind and provide info for widgets @@ -38,11 +47,12 @@ import javax.inject.Inject class CommunalWidgetHost @Inject constructor( + @Background private val bgScope: CoroutineScope, private val appWidgetManager: Optional<AppWidgetManager>, private val appWidgetHost: CommunalAppWidgetHost, private val selectedUserInteractor: SelectedUserInteractor, @CommunalLog logBuffer: LogBuffer, -) { +) : CommunalAppWidgetHost.Observer { companion object { private const val TAG = "CommunalWidgetHost" @@ -60,6 +70,19 @@ constructor( private val logger = Logger(logBuffer, TAG) + private val _appWidgetProviders = MutableStateFlow(emptyMap<Int, AppWidgetProviderInfo?>()) + + /** + * A flow of mappings between an appWidgetId and its corresponding [AppWidgetProviderInfo]. + * These [AppWidgetProviderInfo]s represent app widgets that are actively bound to the + * [CommunalAppWidgetHost]. + * + * The [AppWidgetProviderInfo] may be null in the case that the widget is bound but its provider + * is unavailable. For example, its package is not installed. + */ + val appWidgetProviders: StateFlow<Map<Int, AppWidgetProviderInfo?>> = + _appWidgetProviders.asStateFlow() + /** * Allocate an app widget id and binds the widget with the provider and associated user. * @@ -77,6 +100,7 @@ constructor( ) ) { logger.d("Successfully bound the widget $provider") + onProviderInfoUpdated(id, getAppWidgetInfo(id)) return id } appWidgetHost.deleteAppWidgetId(id) @@ -100,7 +124,83 @@ constructor( return false } + @WorkerThread fun getAppWidgetInfo(widgetId: Int): AppWidgetProviderInfo? { return appWidgetManager.getOrNull()?.getAppWidgetInfo(widgetId) } + + fun startObservingHost() { + appWidgetHost.addObserver(this@CommunalWidgetHost) + } + + fun stopObservingHost() { + appWidgetHost.removeObserver(this@CommunalWidgetHost) + } + + fun refreshProviders() { + bgScope.launch { + val newProviders = mutableMapOf<Int, AppWidgetProviderInfo?>() + appWidgetHost.appWidgetIds.forEach { appWidgetId -> + // Listen for updates from each bound widget + addListener(appWidgetId) + + // Fetch provider info of the widget + newProviders[appWidgetId] = getAppWidgetInfo(appWidgetId) + } + + _appWidgetProviders.value = newProviders.toMap() + } + } + + override fun onHostStartListening() { + refreshProviders() + } + + override fun onHostStopListening() { + // Remove listeners + _appWidgetProviders.value.keys.forEach { appWidgetId -> + appWidgetHost.removeListener(appWidgetId) + } + + // Clear providers + _appWidgetProviders.value = emptyMap() + } + + override fun onAllocateAppWidgetId(appWidgetId: Int) { + addListener(appWidgetId) + } + + override fun onDeleteAppWidgetId(appWidgetId: Int) { + appWidgetHost.removeListener(appWidgetId) + _appWidgetProviders.value = + _appWidgetProviders.value.toMutableMap().also { it.remove(appWidgetId) } + } + + private fun addListener(appWidgetId: Int) { + appWidgetHost.setListener( + appWidgetId, + CommunalAppWidgetHostListener(appWidgetId, this::onProviderInfoUpdated), + ) + } + + private fun onProviderInfoUpdated(appWidgetId: Int, providerInfo: AppWidgetProviderInfo?) { + bgScope.launch { + _appWidgetProviders.value = + _appWidgetProviders.value.toMutableMap().also { it[appWidgetId] = providerInfo } + } + } + + /** A [AppWidgetHostListener] for [appWidgetId]. */ + private class CommunalAppWidgetHostListener( + private val appWidgetId: Int, + private val onUpdateProviderInfo: (Int, AppWidgetProviderInfo?) -> Unit, + ) : AppWidgetHostListener { + override fun onUpdateProviderInfo(providerInfo: AppWidgetProviderInfo?) { + onUpdateProviderInfo(appWidgetId, providerInfo) + } + + override fun onViewDataChanged(viewId: Int) {} + + override fun updateAppWidget(remoteViews: RemoteViews?) {} + } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt index aa6516d54563..2000f96bcdb0 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt @@ -69,16 +69,18 @@ interface CommunalWidgetModule { @SysUISingleton @Provides fun provideCommunalWidgetHost( + @Application applicationScope: CoroutineScope, appWidgetManager: Optional<AppWidgetManager>, appWidgetHost: CommunalAppWidgetHost, selectedUserInteractor: SelectedUserInteractor, @CommunalLog logBuffer: LogBuffer, ): CommunalWidgetHost { return CommunalWidgetHost( + applicationScope, appWidgetManager, appWidgetHost, selectedUserInteractor, - logBuffer + logBuffer, ) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt index 7c2dae34707b..060a3318abd0 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt @@ -25,10 +25,10 @@ import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl import com.android.systemui.util.kotlin.SharedPreferencesExt.observe +import com.android.systemui.util.kotlin.emitOnStart import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onStart class AuthorizedPanelsRepositoryImpl @Inject @@ -40,7 +40,7 @@ constructor( override fun observeAuthorizedPanels(user: UserHandle): Flow<Set<String>> { val prefs = instantiateSharedPrefs(user) - return prefs.observe(KEY).onStart { emit(Unit) }.map { getAuthorizedPanelsInternal(prefs) } + return prefs.observe().emitOnStart().map { getAuthorizedPanelsInternal(prefs) } } override fun getAuthorizedPanels(): Set<String> { diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt index 9be049400962..691ec76d50ed 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt @@ -26,12 +26,12 @@ import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl import com.android.systemui.util.kotlin.SharedPreferencesExt.observe +import com.android.systemui.util.kotlin.emitOnStart import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onStart @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) @SysUISingleton @@ -63,8 +63,8 @@ constructor( ): Flow<SelectedComponentRepository.SelectedComponent?> { val prefs = getSharedPreferencesForUser(userHandle.identifier) return prefs - .observe(PREF_COMPONENT) - .onStart { emit(Unit) } + .observe() + .emitOnStart() .map { getSelectedComponent(userHandle) } .flowOn(bgDispatcher) } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index ef3f10fa3853..11e6f7a8c38c 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -49,6 +49,7 @@ import android.content.SharedPreferences; import android.content.om.OverlayManager; import android.content.pm.IPackageManager; import android.content.pm.LauncherApps; +import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.ShortcutManager; import android.content.res.AssetManager; @@ -224,6 +225,13 @@ public class FrameworkServicesModule { @Provides @Singleton + static UserScopedService<ColorDisplayManager> provideScopedColorDisplayManager( + Context context) { + return new UserScopedServiceImpl<>(context, ColorDisplayManager.class); + } + + @Provides + @Singleton static CrossWindowBlurListeners provideCrossWindowBlurListeners() { return CrossWindowBlurListeners.getInstance(); } @@ -483,6 +491,12 @@ public class FrameworkServicesModule { @Provides @Singleton + static PackageInstaller providePackageInstaller(PackageManager packageManager) { + return packageManager.getPackageInstaller(); + } + + @Provides + @Singleton static PackageManagerWrapper providePackageManagerWrapper() { return PackageManagerWrapper.getInstance(); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java index 0fd688760a32..8c3de4bd1928 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java +++ b/packages/SystemUI/src/com/android/systemui/doze/dagger/DozeModule.java @@ -89,8 +89,8 @@ public abstract class DozeModule { dozeFalsingManagerAdapter, dozeTriggers, dozeUi, - dozeScreenState, dozeScreenBrightness, + dozeScreenState, dozeWallpaperState, dozeDockHandler, dozeAuthRemover, diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java index 8c0a73ceb4d6..6e043391c197 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java @@ -198,7 +198,6 @@ public class DreamOverlayContainerViewController extends mLowLightTransitionCoordinator = lowLightTransitionCoordinator; mBouncerlessScrimController = bouncerlessScrimController; - mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback); mKeyguardTransitionInteractor = keyguardTransitionInteractor; @@ -234,6 +233,7 @@ public class DreamOverlayContainerViewController extends mJitterStartTimeMillis = System.currentTimeMillis(); mHandler.postDelayed(this::updateBurnInOffsets, mBurnInProtectionUpdateInterval); mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback); + mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback); final Region emptyRegion = Region.obtain(); mView.getRootSurfaceControl().setTouchableRegion(emptyRegion); emptyRegion.recycle(); @@ -255,8 +255,9 @@ public class DreamOverlayContainerViewController extends @Override protected void onViewDetached() { - mHandler.removeCallbacks(this::updateBurnInOffsets); + mHandler.removeCallbacksAndMessages(null); mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback); + mBouncerlessScrimController.removeCallback(mBouncerlessExpansionCallback); mDreamOverlayAnimationsController.cancelAnimations(); } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt index 74452d1980be..20341389b75d 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/domain/interactor/HomeControlsComponentInteractor.kt @@ -21,7 +21,6 @@ import android.app.DreamManager import android.content.ComponentName import android.os.PowerManager import android.os.UserHandle -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.domain.interactor.PackageChangeInteractor import com.android.systemui.common.shared.model.PackageChangeModel import com.android.systemui.controls.ControlsServiceInfo @@ -36,6 +35,7 @@ import com.android.systemui.util.kotlin.getOrNull import com.android.systemui.util.kotlin.pairwiseBy import com.android.systemui.util.kotlin.sample import com.android.systemui.util.time.SystemClock +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import javax.inject.Inject import kotlin.math.abs import kotlin.time.Duration.Companion.milliseconds @@ -132,7 +132,7 @@ constructor( ?: panels.firstOrNull() item?.panelActivity } - .stateIn(bgScope, SharingStarted.WhileSubscribed(), null) + .stateIn(bgScope, SharingStarted.Eagerly, null) private val taskFragmentFinished = MutableSharedFlow<Long>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index 95bc514540f5..49be03cb08ad 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -2739,12 +2739,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene protected final void setRotationSuggestionsEnabled(boolean enabled) { try { final int userId = Binder.getCallingUserHandle().getIdentifier(); - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - if (enabled) { - info.setRotationSuggestionDisabled(true); - } - mStatusBarService.disableForUser(info, mToken, mContext.getPackageName(), userId, - "setRotationSuggestionsEnabled"); + final int what = enabled + ? StatusBarManager.DISABLE2_NONE + : StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS; + mStatusBarService.disable2ForUser(what, mToken, mContext.getPackageName(), userId); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index d6fd354d8544..2cda72809a36 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -16,7 +16,6 @@ package com.android.systemui.keyguard; -import static android.app.StatusBarManager.DISABLE2_NONE; import static android.app.StatusBarManager.SESSION_KEYGUARD; import static android.provider.Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT; import static android.provider.Settings.System.LOCKSCREEN_SOUNDS_ENABLED; @@ -3440,12 +3439,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // unless disable is called to show un-hide it once first if (forceClearFlags) { try { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(flags, - DISABLE2_NONE); - mStatusBarService.disableForUser(info, mStatusBarDisableToken, + mStatusBarService.disableForUser(flags, mStatusBarDisableToken, mContext.getPackageName(), - mSelectedUserInteractor.getSelectedUserId(true), - "adjustStatusBarLocked - force clear flags"); + mSelectedUserInteractor.getSelectedUserId(true)); } catch (RemoteException e) { Log.d(TAG, "Failed to force clear flags", e); } @@ -3471,11 +3467,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } try { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(flags, - DISABLE2_NONE); - mStatusBarService.disableForUser(info, mStatusBarDisableToken, - mContext.getPackageName(), mSelectedUserInteractor.getSelectedUserId(true), - "adjustStatusBarLocked - set disable flags"); + mStatusBarService.disableForUser(flags, mStatusBarDisableToken, + mContext.getPackageName(), + mSelectedUserInteractor.getSelectedUserId(true)); } catch (RemoteException e) { Log.d(TAG, "Failed to set disable flags: " + flags, e); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt index e32bfcf81fe2..7655d7a89dc3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt @@ -134,7 +134,7 @@ constructor( TransitionInfo( ownerName = "", from = KeyguardState.OFF, - to = KeyguardState.LOCKSCREEN, + to = KeyguardState.OFF, animator = null ) ) @@ -247,7 +247,7 @@ constructor( state: TransitionState ) { if (updateTransitionId != transitionId) { - Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId") + Log.w(TAG, "Attempting to update with old/invalid transitionId: $transitionId") return } @@ -266,6 +266,14 @@ constructor( } override suspend fun emitInitialStepsFromOff(to: KeyguardState) { + _currentTransitionInfo.value = + TransitionInfo( + ownerName = "KeyguardTransitionRepository(boot)", + from = KeyguardState.OFF, + to = to, + animator = null + ) + emitTransition( TransitionStep( KeyguardState.OFF, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt new file mode 100644 index 000000000000..80bdc65f9b97 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.repository + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.shared.model.KeyguardState +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow + +@SysUISingleton +class LockscreenSceneTransitionRepository @Inject constructor() { + + /** + * This [KeyguardState] will indicate which sub state within KTF should be navigated to when the + * next transition into the Lockscreen scene is started. It will be consumed exactly once and + * after that the state will be set back to [DEFAULT_STATE]. + */ + val nextLockscreenTargetState: MutableStateFlow<KeyguardState> = MutableStateFlow(DEFAULT_STATE) + + companion object { + val DEFAULT_STATE = KeyguardState.LOCKSCREEN + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt index 5a28f7113ebd..9b07675f672c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt @@ -26,6 +26,7 @@ import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine import com.android.wm.shell.animation.Interpolators import javax.inject.Inject @@ -140,6 +141,8 @@ constructor( } private fun listenForAlternateBouncerToGone() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return if (KeyguardWmStateRefactor.isEnabled) { // Handled via #dismissAlternateBouncer. return @@ -162,6 +165,8 @@ constructor( } private fun listenForAlternateBouncerToPrimaryBouncer() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return scope.launch { keyguardInteractor.primaryBouncerShowing .filterRelevantKeyguardStateAnd { isPrimaryBouncerShowing -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index 4d737749fbc1..a306954b7d8f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -28,6 +28,7 @@ import com.android.systemui.keyguard.shared.model.BiometricUnlockMode.Companion. import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.Utils.Companion.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -185,6 +186,7 @@ constructor( * PRIMARY_BOUNCER. */ private fun listenForAodToPrimaryBouncer() { + if (SceneContainerFlag.isEnabled) return scope.launch("$TAG#listenForAodToPrimaryBouncer") { keyguardInteractor.primaryBouncerShowing .filterRelevantKeyguardStateAnd { primaryBouncerShowing -> primaryBouncerShowing } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt index e738ea4397de..63294f7609a2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt @@ -26,6 +26,7 @@ import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -93,6 +94,8 @@ constructor( } private fun listenForDreamingLockscreenHostedToPrimaryBouncer() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return scope.launch { keyguardInteractor.primaryBouncerShowing .filterRelevantKeyguardStateAnd { isBouncerShowing -> isBouncerShowing } @@ -101,6 +104,8 @@ constructor( } private fun listenForDreamingLockscreenHostedToGone() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return scope.launch { keyguardInteractor.biometricUnlockState .filterRelevantKeyguardStateAnd { biometricUnlockState -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index c952e0879d12..7961b45830d4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -29,6 +29,7 @@ import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine import com.android.systemui.util.kotlin.sample import javax.inject.Inject @@ -88,6 +89,8 @@ constructor( private fun listenForDreamingToGlanceableHub() { if (!communalHub()) return + if (SceneContainerFlag.isEnabled) + return // TODO(b/336576536): Check if adaptation for scene framework is needed scope.launch("$TAG#listenForDreamingToGlanceableHub", mainDispatcher) { glanceableHubTransitions.listenForGlanceableHubTransition( transitionOwnerName = TAG, @@ -175,6 +178,8 @@ constructor( } private fun listenForDreamingToGoneWhenDismissable() { + if (SceneContainerFlag.isEnabled) + return // TODO(b/336576536): Check if adaptation for scene framework is needed scope.launch { keyguardInteractor.isAbleToDream .sampleCombine( @@ -190,6 +195,8 @@ constructor( } private fun listenForDreamingToGoneFromBiometricUnlock() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return scope.launch { keyguardInteractor.biometricUnlockState .filterRelevantKeyguardStateAnd { biometricUnlockState -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt index faab033441c1..ca6ab3ef52d8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt @@ -28,9 +28,11 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf import com.android.systemui.util.kotlin.BooleanFlowOperators.not import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -62,6 +64,8 @@ constructor( ) { override fun start() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return if (!Flags.communalHub()) { return } @@ -79,6 +83,7 @@ constructor( duration = when (toState) { KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION + KeyguardState.OCCLUDED -> TO_OCCLUDED_DURATION else -> DEFAULT_DURATION }.inWholeMilliseconds } @@ -171,5 +176,6 @@ constructor( const val TAG = "FromGlanceableHubTransitionInteractor" val DEFAULT_DURATION = 1.seconds val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION + val TO_OCCLUDED_DURATION = 450.milliseconds } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt index c2c095bb9574..2b3732f75812 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt @@ -29,6 +29,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -62,6 +63,8 @@ constructor( ) { override fun start() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return listenForGoneToAodOrDozing() listenForGoneToDreaming() listenForGoneToLockscreenOrHub() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 56261e0865e1..dad2d9692dbc 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -20,6 +20,7 @@ import android.animation.ValueAnimator import android.util.MathUtils import com.android.app.animation.Interpolators import com.android.app.tracing.coroutines.launch +import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -32,6 +33,7 @@ import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine import java.util.UUID @@ -150,6 +152,7 @@ constructor( } private fun listenForLockscreenToPrimaryBouncer() { + if (SceneContainerFlag.isEnabled) return scope.launch("$TAG#listenForLockscreenToPrimaryBouncer") { keyguardInteractor.primaryBouncerShowing .filterRelevantKeyguardStateAnd { isBouncerShowing -> isBouncerShowing } @@ -174,6 +177,7 @@ constructor( /* Starts transitions when manually dragging up the bouncer from the lockscreen. */ private fun listenForLockscreenToPrimaryBouncerDragging() { + if (SceneContainerFlag.isEnabled) return var transitionId: UUID? = null scope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") { shadeRepository.legacyShadeExpansion @@ -280,6 +284,7 @@ constructor( } private fun listenForLockscreenToGoneDragging() { + if (SceneContainerFlag.isEnabled) return if (KeyguardWmStateRefactor.isEnabled) { // When the refactor is enabled, we no longer use isKeyguardGoingAway. scope.launch("$TAG#listenForLockscreenToGoneDragging") { @@ -337,7 +342,9 @@ constructor( * keyguard transition. */ private fun listenForLockscreenToGlanceableHub() { - if (!com.android.systemui.Flags.communalHub()) { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return + if (!Flags.communalHub()) { return } scope.launch(mainDispatcher) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt index e51ba8308a08..2603aab2781b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import com.android.app.animation.Interpolators +import com.android.systemui.Flags.restartDreamOnUnocclude import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background @@ -26,6 +27,7 @@ import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.Utils.Companion.sample import com.android.systemui.util.kotlin.sample import javax.inject.Inject @@ -90,17 +92,14 @@ constructor( .sample( communalInteractor.isIdleOnCommunal, communalInteractor.showCommunalFromOccluded, + communalInteractor.dreamFromOccluded, ) - .collect { (_, isIdleOnCommunal, showCommunalFromOccluded) -> - // Occlusion signals come from the framework, and should interrupt any - // existing transition - val to = - if (isIdleOnCommunal || showCommunalFromOccluded) { - KeyguardState.GLANCEABLE_HUB - } else { - KeyguardState.LOCKSCREEN - } - startTransitionTo(to) + .collect { (_, isIdleOnCommunal, showCommunalFromOccluded, dreamFromOccluded) -> + startTransitionToLockscreenOrHub( + isIdleOnCommunal, + showCommunalFromOccluded, + dreamFromOccluded + ) } } } else { @@ -110,26 +109,42 @@ constructor( keyguardInteractor.isKeyguardShowing, communalInteractor.isIdleOnCommunal, communalInteractor.showCommunalFromOccluded, + communalInteractor.dreamFromOccluded, ) - .filterRelevantKeyguardStateAnd { (isOccluded, isShowing, _, _) -> + .filterRelevantKeyguardStateAnd { (isOccluded, isShowing, _, _, _) -> !isOccluded && isShowing } - .collect { (_, _, isIdleOnCommunal, showCommunalFromOccluded) -> - // Occlusion signals come from the framework, and should interrupt any - // existing transition - val to = - if (isIdleOnCommunal || showCommunalFromOccluded) { - KeyguardState.GLANCEABLE_HUB - } else { - KeyguardState.LOCKSCREEN - } - startTransitionTo(to) + .collect { (_, _, isIdleOnCommunal, showCommunalFromOccluded, dreamFromOccluded) + -> + startTransitionToLockscreenOrHub( + isIdleOnCommunal, + showCommunalFromOccluded, + dreamFromOccluded + ) } } } } + private suspend fun FromOccludedTransitionInteractor.startTransitionToLockscreenOrHub( + isIdleOnCommunal: Boolean, + showCommunalFromOccluded: Boolean, + dreamFromOccluded: Boolean, + ) { + if (restartDreamOnUnocclude() && dreamFromOccluded) { + startTransitionTo(KeyguardState.DREAMING) + } else if (isIdleOnCommunal || showCommunalFromOccluded) { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return + startTransitionTo(KeyguardState.GLANCEABLE_HUB) + } else { + startTransitionTo(KeyguardState.LOCKSCREEN) + } + } + private fun listenForOccludedToGone() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return if (KeyguardWmStateRefactor.isEnabled) { // We don't think OCCLUDED to GONE is possible. You should always have to go via a // *_BOUNCER state to end up GONE. Launching an activity over a dismissable keyguard @@ -150,10 +165,6 @@ constructor( } } - fun dismissToGone() { - scope.launch { startTransitionTo(KeyguardState.GONE) } - } - private fun listenForOccludedToAsleep() { scope.launch { listenForSleepTransition() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index 181a551b0537..53a0c3200f3d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -28,6 +28,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.kotlin.Utils.Companion.sample import com.android.systemui.util.kotlin.sample @@ -98,6 +99,8 @@ constructor( } private fun listenForPrimaryBouncerToLockscreenHubOrOccluded() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return if (KeyguardWmStateRefactor.isEnabled) { scope.launch { keyguardInteractor.primaryBouncerShowing @@ -158,10 +161,14 @@ constructor( } private fun listenForPrimaryBouncerToAsleep() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return scope.launch { listenForSleepTransition() } } private fun listenForPrimaryBouncerToDreamingLockscreenHosted() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return scope.launch { keyguardInteractor.primaryBouncerShowing .sample(keyguardInteractor.isActiveDreamLockscreenHosted, ::Pair) @@ -174,6 +181,8 @@ constructor( } private fun listenForPrimaryBouncerToGone() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return if (KeyguardWmStateRefactor.isEnabled) { // This is handled in KeyguardSecurityContainerController and // StatusBarKeyguardViewManager, which calls the transition interactor to kick off a diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt index 197221a7b5b3..fcf67d519cae 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GlanceableHubTransitions.kt @@ -25,6 +25,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.sample import java.util.UUID import javax.inject.Inject @@ -49,6 +50,8 @@ constructor( fromState: KeyguardState, toState: KeyguardState, ) { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return val toScene = if (fromState == KeyguardState.GLANCEABLE_HUB) { CommunalScenes.Blank diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 88367f42abec..2d7b7375f625 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -55,7 +55,6 @@ import javax.inject.Provider import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -63,10 +62,10 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combineTransform +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge @@ -179,12 +178,7 @@ constructor( isDreaming && isDozeOff(dozeTransitionModel.to) } .sample(powerInteractor.isAwake) { isAbleToDream, isAwake -> isAbleToDream && isAwake } - .flatMapLatest { isAbleToDream -> - flow { - delay(50) - emit(isAbleToDream) - } - } + .debounce(50L) .distinctUntilChanged() /** Whether the keyguard is showing or not. */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index a18579d9c8e0..30c6718adf1b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -17,6 +17,8 @@ package com.android.systemui.keyguard.domain.interactor +import android.annotation.FloatRange +import android.annotation.SuppressLint import android.util.Log import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -28,12 +30,13 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCE import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN -import com.android.systemui.keyguard.shared.model.KeyguardState.OFF import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.kotlin.pairwise +import java.util.UUID import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -76,6 +79,8 @@ constructor( * single state. This prevent the redundant filters from running. */ private val transitionValueCache = mutableMapOf<KeyguardState, MutableSharedFlow<Float>>() + + @SuppressLint("SharedFlowCreation") private fun getTransitionValueFlow(state: KeyguardState): MutableSharedFlow<Float> { return transitionValueCache.getOrPut(state) { MutableSharedFlow<Float>( @@ -90,6 +95,9 @@ constructor( @Deprecated("Not performant - Use something else in this class") val transitions = repository.transitions + val transitionState: StateFlow<TransitionStep> = + transitions.stateIn(scope, SharingStarted.Eagerly, TransitionStep()) + /** * A pair of the most recent STARTED step, and the transition step immediately preceding it. The * transition framework enforces that the previous step is either a CANCELED or FINISHED step, @@ -99,6 +107,7 @@ constructor( * FINISHED. In the case of a CANCELED step, we can also figure out which state we were coming * from when we were canceled. */ + @SuppressLint("SharedFlowCreation") val startedStepWithPrecedingStep = repository.transitions .pairwise() @@ -144,9 +153,10 @@ constructor( } /** Given an [edge], return a SharedFlow to collect only relevant [TransitionStep]. */ + @SuppressLint("SharedFlowCreation") fun getOrCreateFlow(edge: Edge): MutableSharedFlow<TransitionStep> { return transitionMap.getOrPut(edge) { - MutableSharedFlow<TransitionStep>( + MutableSharedFlow( extraBufferCapacity = 10, onBufferOverflow = BufferOverflow.DROP_OLDEST ) @@ -180,6 +190,7 @@ constructor( * AOD<->* transition information, mapped to dozeAmount range of AOD (1f) <-> * * (0f). */ + @SuppressLint("SharedFlowCreation") val dozeAmountTransition: Flow<TransitionStep> = repository.transitions .filter { step -> step.from == AOD || step.to == AOD } @@ -201,11 +212,20 @@ constructor( repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED } /** The destination state of the last [TransitionState.STARTED] transition. */ + @SuppressLint("SharedFlowCreation") val startedKeyguardState: SharedFlow<KeyguardState> = startedKeyguardTransitionStep .map { step -> step.to } .shareIn(scope, SharingStarted.Eagerly, replay = 1) + /** The from state of the last [TransitionState.STARTED] transition. */ + // TODO: is it performant to have several SharedFlows side by side instead of one? + @SuppressLint("SharedFlowCreation") + val startedKeyguardFromState: SharedFlow<KeyguardState> = + startedKeyguardTransitionStep + .map { step -> step.from } + .shareIn(scope, SharingStarted.Eagerly, replay = 1) + /** Which keyguard state to use when the device goes to sleep. */ val asleepKeyguardState: StateFlow<KeyguardState> = keyguardRepository.isAodAvailable @@ -243,6 +263,7 @@ constructor( * sufficient. However, if you're having issues with state *during* transitions started after * one or more canceled transitions, you probably need to use [currentKeyguardState]. */ + @SuppressLint("SharedFlowCreation") val finishedKeyguardState: SharedFlow<KeyguardState> = finishedKeyguardTransitionStep .map { step -> step.to } @@ -356,6 +377,8 @@ constructor( * state. */ fun startDismissKeyguardTransition() { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return when (val startedState = startedKeyguardState.replayCache.last()) { LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard() PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer() @@ -489,7 +512,19 @@ constructor( return startedKeyguardState.replayCache.last() } + fun getStartedFromState(): KeyguardState { + return startedKeyguardFromState.replayCache.last() + } + fun getFinishedState(): KeyguardState { return finishedKeyguardState.replayCache.last() } + + suspend fun startTransition(info: TransitionInfo) = repository.startTransition(info) + + fun updateTransition( + transitionId: UUID, + @FloatRange(from = 0.0, to = 1.0) value: Float, + state: TransitionState + ) = repository.updateTransition(transitionId, value, state) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt index 2d944c694310..9443570705c8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt @@ -103,6 +103,7 @@ constructor( KeyguardState.LOCKSCREEN -> true KeyguardState.GONE -> true KeyguardState.OCCLUDED -> true + KeyguardState.UNDEFINED -> true } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt index d95c38e2697c..3c661861efd2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.CoreStartable +import com.android.systemui.keyguard.domain.interactor.scenetransition.LockscreenSceneTransitionInteractor import dagger.Binds import dagger.Module import dagger.multibindings.ClassKey @@ -31,6 +32,13 @@ abstract class StartKeyguardTransitionModule { abstract fun bind(impl: KeyguardTransitionCoreStartable): CoreStartable @Binds + @IntoMap + @ClassKey(LockscreenSceneTransitionInteractor::class) + abstract fun bindLockscreenSceneTransitionInteractor( + impl: LockscreenSceneTransitionInteractor + ): CoreStartable + + @Binds @IntoSet abstract fun fromPrimaryBouncer( impl: FromPrimaryBouncerTransitionInteractor diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt index bb2eeb77969d..dc35e4311d25 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt @@ -16,11 +16,16 @@ package com.android.systemui.keyguard.domain.interactor +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor +import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -42,6 +47,7 @@ constructor( fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor, fromAlternateBouncerInteractor: FromAlternateBouncerTransitionInteractor, notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor, + sceneInteractor: SceneInteractor, ) { private val defaultSurfaceBehindVisibility = transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible) @@ -103,21 +109,42 @@ constructor( * animation. This is used to keep the RemoteAnimationTarget alive until we're done using it. */ val usingKeyguardGoingAwayAnimation: Flow<Boolean> = - combine( - transitionInteractor.isInTransitionToState(KeyguardState.GONE), - transitionInteractor.finishedKeyguardState, - surfaceBehindInteractor.isAnimatingSurface, - notificationLaunchAnimationInteractor.isLaunchAnimationRunning, - ) { isInTransitionToGone, finishedState, isAnimatingSurface, notifLaunchRunning -> - // Using the animation if we're animating it directly, or if the - // ActivityLaunchAnimator is in the process of animating it. - val animationsRunning = isAnimatingSurface || notifLaunchRunning - // We may still be animating the surface after the keyguard is fully GONE, since - // some animations (like the translation spring) are not tied directly to the - // transition step amount. - isInTransitionToGone || (finishedState == KeyguardState.GONE && animationsRunning) - } - .distinctUntilChanged() + if (SceneContainerFlag.isEnabled) { + combine( + sceneInteractor.transitionState, + surfaceBehindInteractor.isAnimatingSurface, + notificationLaunchAnimationInteractor.isLaunchAnimationRunning, + ) { transition, isAnimatingSurface, isLaunchAnimationRunning -> + // Using the animation if we're animating it directly, or if the + // ActivityLaunchAnimator is in the process of animating it. + val isAnyAnimationRunning = isAnimatingSurface || isLaunchAnimationRunning + // We may still be animating the surface after the keyguard is fully GONE, since + // some animations (like the translation spring) are not tied directly to the + // transition step amount. + transition.isTransitioning(to = Scenes.Gone) || + (isAnyAnimationRunning && + (transition.isIdle(Scenes.Gone) || + transition.isTransitioning(from = Scenes.Gone))) + } + .distinctUntilChanged() + } else { + combine( + transitionInteractor.isInTransitionToState(KeyguardState.GONE), + transitionInteractor.finishedKeyguardState, + surfaceBehindInteractor.isAnimatingSurface, + notificationLaunchAnimationInteractor.isLaunchAnimationRunning, + ) { isInTransitionToGone, finishedState, isAnimatingSurface, notifLaunchRunning -> + // Using the animation if we're animating it directly, or if the + // ActivityLaunchAnimator is in the process of animating it. + val animationsRunning = isAnimatingSurface || notifLaunchRunning + // We may still be animating the surface after the keyguard is fully GONE, since + // some animations (like the translation spring) are not tied directly to the + // transition step amount. + isInTransitionToGone || + (finishedState == KeyguardState.GONE && animationsRunning) + } + .distinctUntilChanged() + } /** * Whether the lockscreen is visible, from the Window Manager (WM) perspective. @@ -127,28 +154,44 @@ constructor( * want to know if the AOD/clock/notifs/etc. are visible. */ val lockscreenVisibility: Flow<Boolean> = - transitionInteractor.currentKeyguardState - .sample(transitionInteractor.startedStepWithPrecedingStep, ::Pair) - .map { (currentState, startedWithPrev) -> - val startedFromStep = startedWithPrev?.previousValue - val startedStep = startedWithPrev?.newValue - val returningToGoneAfterCancellation = - startedStep?.to == KeyguardState.GONE && - startedFromStep?.transitionState == TransitionState.CANCELED && - startedFromStep.from == KeyguardState.GONE + if (SceneContainerFlag.isEnabled) { + sceneInteractor.transitionState + .pairwise(ObservableTransitionState.Idle(Scenes.Lockscreen)) + .map { (prevTransitionState, transitionState) -> + val isReturningToGoneAfterCancellation = + prevTransitionState.isTransitioning(from = Scenes.Gone) && + transitionState.isTransitioning(to = Scenes.Gone) + val isNotOnGone = + !transitionState.isTransitioning(from = Scenes.Gone) && + !transitionState.isIdle(Scenes.Gone) - if (!returningToGoneAfterCancellation) { - // By default, apply the lockscreen visibility of the current state. - KeyguardState.lockscreenVisibleInState(currentState) - } else { - // If we're transitioning to GONE after a prior canceled transition from GONE, - // then this is the camera launch transition from an asleep state back to GONE. - // We don't want to show the lockscreen since we're aborting the lock and going - // back to GONE. - KeyguardState.lockscreenVisibleInState(KeyguardState.GONE) + isNotOnGone && !isReturningToGoneAfterCancellation } - } - .distinctUntilChanged() + .distinctUntilChanged() + } else { + transitionInteractor.currentKeyguardState + .sample(transitionInteractor.startedStepWithPrecedingStep, ::Pair) + .map { (currentState, startedWithPrev) -> + val startedFromStep = startedWithPrev?.previousValue + val startedStep = startedWithPrev?.newValue + val returningToGoneAfterCancellation = + startedStep?.to == KeyguardState.GONE && + startedFromStep?.transitionState == TransitionState.CANCELED && + startedFromStep.from == KeyguardState.GONE + + if (!returningToGoneAfterCancellation) { + // By default, apply the lockscreen visibility of the current state. + KeyguardState.lockscreenVisibleInState(currentState) + } else { + // If we're transitioning to GONE after a prior canceled transition from + // GONE, then this is the camera launch transition from an asleep state back + // to GONE. We don't want to show the lockscreen since we're aborting the + // lock and going back to GONE. + KeyguardState.lockscreenVisibleInState(KeyguardState.GONE) + } + } + .distinctUntilChanged() + } /** * Whether always-on-display (AOD) is visible when the lockscreen is visible, from window diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt new file mode 100644 index 000000000000..6e00aa7956e7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor.scenetransition + +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.LockscreenSceneTransitionRepository +import com.android.systemui.keyguard.data.repository.LockscreenSceneTransitionRepository.Companion.DEFAULT_STATE +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled +import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED +import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.util.kotlin.pairwise +import java.util.UUID +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +/** + * This class listens to scene framework scene transitions and manages keyguard transition framework + * (KTF) states accordingly. + * + * There are a few rules: + * - When scene framework is on a scene outside of Lockscreen, then KTF is in state UNDEFINED + * - When scene framework is on Lockscreen, KTF is allowed to change its scenes freely + * - When scene framework is transitioning away from Lockscreen, then KTF transitions to UNDEFINED + * and shares its progress. + * - When scene framework is transitioning to Lockscreen, then KTF starts a transition to LOCKSCREEN + * but it is allowed to interrupt this transition and transition to other internal KTF states + * + * There are a few notable differences between SceneTransitionLayout (STL) and KTF that require + * special treatment when synchronizing both state machines. + * - STL does not emit cancelations as KTF does + * - Both STL and KTF require state continuity, though the rules from where starting the next + * transition is allowed is different on each side: + * - STL has a concept of "currentScene" which can be chosen to be either A or B in a A -> B + * transition. The currentScene determines which transition can be started next. In KTF the + * currentScene is always the `to` state. Which means transitions can only be started from B. + * This also holds true when A -> B was canceled: the next transition needs to start from B. + * - KTF can not settle back in its from scene, instead it needs to cancel and start a reversed + * transition. + */ +@SysUISingleton +class LockscreenSceneTransitionInteractor +@Inject +constructor( + val transitionInteractor: KeyguardTransitionInteractor, + @Application private val applicationScope: CoroutineScope, + private val sceneInteractor: SceneInteractor, + private val repository: LockscreenSceneTransitionRepository, +) : CoreStartable, SceneInteractor.OnSceneAboutToChangeListener { + + private var currentTransitionId: UUID? = null + private var progressJob: Job? = null + + override fun start() { + sceneInteractor.registerSceneStateProcessor(this) + listenForSceneTransitionProgress() + } + + override fun onSceneAboutToChange(toScene: SceneKey, sceneState: Any?) { + if (toScene != Scenes.Lockscreen || sceneState == null) return + if (sceneState !is KeyguardState) { + throw IllegalArgumentException("Lockscreen sceneState needs to be a KeyguardState.") + } + repository.nextLockscreenTargetState.value = sceneState + } + + private fun listenForSceneTransitionProgress() { + applicationScope.launch { + sceneInteractor.transitionState + .pairwise(ObservableTransitionState.Idle(Scenes.Lockscreen)) + .collect { (prevTransition, transition) -> + when (transition) { + is ObservableTransitionState.Idle -> handleIdle(prevTransition, transition) + is ObservableTransitionState.Transition -> handleTransition(transition) + } + } + } + } + + private suspend fun handleIdle( + prevTransition: ObservableTransitionState, + idle: ObservableTransitionState.Idle + ) { + if (currentTransitionId == null) return + if (prevTransition !is ObservableTransitionState.Transition) return + + if (idle.currentScene == prevTransition.toScene) { + finishCurrentTransition() + } else { + val targetState = + if (idle.currentScene == Scenes.Lockscreen) { + transitionInteractor.getStartedFromState() + } else { + UNDEFINED + } + finishReversedTransitionTo(targetState) + } + } + + private fun finishCurrentTransition() { + transitionInteractor.updateTransition(currentTransitionId!!, 1f, FINISHED) + resetTransitionData() + } + + private suspend fun finishReversedTransitionTo(state: KeyguardState) { + val newTransition = + TransitionInfo( + ownerName = this::class.java.simpleName, + from = transitionInteractor.getStartedState(), + to = state, + animator = null, + modeOnCanceled = TransitionModeOnCanceled.REVERSE + ) + currentTransitionId = transitionInteractor.startTransition(newTransition) + transitionInteractor.updateTransition(currentTransitionId!!, 1f, FINISHED) + resetTransitionData() + } + + private fun resetTransitionData() { + progressJob?.cancel() + progressJob = null + currentTransitionId = null + } + + private suspend fun handleTransition(transition: ObservableTransitionState.Transition) { + if (transition.fromScene == Scenes.Lockscreen) { + if (currentTransitionId != null) { + val currentToState = transitionInteractor.getStartedState() + if (currentToState == UNDEFINED) { + transitionKtfTo(transitionInteractor.getStartedFromState()) + } + } + startTransitionFromLockscreen() + collectProgress(transition) + } else if (transition.toScene == Scenes.Lockscreen) { + if (currentTransitionId != null) { + transitionKtfTo(UNDEFINED) + } + startTransitionToLockscreen() + collectProgress(transition) + } else { + transitionKtfTo(UNDEFINED) + } + } + + private suspend fun transitionKtfTo(state: KeyguardState) { + val currentTransition = transitionInteractor.transitionState.value + if (currentTransition.isFinishedIn(state)) { + // This is already the state we want to be in + resetTransitionData() + } else if (currentTransition.isTransitioning(to = state)) { + finishCurrentTransition() + } else { + finishReversedTransitionTo(state) + } + } + + private fun collectProgress(transition: ObservableTransitionState.Transition) { + progressJob?.cancel() + progressJob = applicationScope.launch { transition.progress.collect { updateProgress(it) } } + } + + private suspend fun startTransitionToLockscreen() { + val newTransition = + TransitionInfo( + ownerName = this::class.java.simpleName, + from = UNDEFINED, + to = repository.nextLockscreenTargetState.value, + animator = null, + modeOnCanceled = TransitionModeOnCanceled.RESET + ) + repository.nextLockscreenTargetState.value = DEFAULT_STATE + startTransition(newTransition) + } + + private suspend fun startTransitionFromLockscreen() { + val currentState = transitionInteractor.getStartedState() + val newTransition = + TransitionInfo( + ownerName = this::class.java.simpleName, + from = currentState, + to = UNDEFINED, + animator = null, + modeOnCanceled = TransitionModeOnCanceled.RESET + ) + startTransition(newTransition) + } + + private suspend fun startTransition(transitionInfo: TransitionInfo) { + if (currentTransitionId != null) { + resetTransitionData() + } + currentTransitionId = transitionInteractor.startTransition(transitionInfo) + } + + private fun updateProgress(progress: Float) { + if (currentTransitionId == null) return + transitionInteractor.updateTransition( + currentTransitionId!!, + progress.coerceIn(0f, 1f), + RUNNING + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt index 7d0553937f25..6d96db34e23a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt @@ -17,7 +17,7 @@ package com.android.systemui.keyguard.shared.model /** List of all possible states to transition to/from */ enum class KeyguardState { - /* + /** * The display is completely off, as well as any sensors that would trigger the device to wake * up. */ @@ -29,13 +29,13 @@ enum class KeyguardState { * notifications is enabled, allowing the device to quickly wake up. */ DOZING, - /* + /** * A device state after the device times out, which can be from both LOCKSCREEN or GONE states. * DOZING is an example of special version of this state. Dreams may be implemented by third * parties to present their own UI over keyguard, like a screensaver. */ DREAMING, - /* + /** * A device state after the device times out, which can be from both LOCKSCREEN or GONE states. * It is a special version of DREAMING state but not DOZING. The active dream will be windowless * and hosted in the lockscreen. @@ -47,17 +47,17 @@ enum class KeyguardState { * low-power mode without a UI, then it is DOZING. */ AOD, - /* + /** * The security screen prompt containing UI to prompt the user to use a biometric credential * (ie: fingerprint). When supported, this may show before showing the primary bouncer. */ ALTERNATE_BOUNCER, - /* + /** * The security screen prompt UI, containing PIN, Password, Pattern for the user to verify their * credentials. */ PRIMARY_BOUNCER, - /* + /** * Device is actively displaying keyguard UI and is not in low-power mode. Device may be * unlocked if SWIPE security method is used, or if face lockscreen bypass is false. */ @@ -68,15 +68,20 @@ enum class KeyguardState { * or dream, as well as swipe down for the notifications and up for the bouncer. */ GLANCEABLE_HUB, - /* - * Keyguard is no longer visible. In most cases the user has just authenticated and keyguard - * is being removed, but there are other cases where the user is swiping away keyguard, such as + /** + * Keyguard is no longer visible. In most cases the user has just authenticated and keyguard is + * being removed, but there are other cases where the user is swiping away keyguard, such as * with SWIPE security method or face unlock without bypass. */ GONE, - /* - * An activity is displaying over the keyguard. + /** + * Only used in scene framework. This means we are currently on any scene framework scene that + * is not Lockscreen. Transitions to and from UNDEFINED are always bound to the + * [SceneTransitionLayout] scene transition that either transitions to or from the Lockscreen + * scene. These transitions are automatically handled by [LockscreenSceneTransitionInteractor]. */ + UNDEFINED, + /** An activity is displaying over the keyguard. */ OCCLUDED; companion object { @@ -109,6 +114,7 @@ enum class KeyguardState { LOCKSCREEN -> true GONE -> true OCCLUDED -> true + UNDEFINED -> true } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt index 0fa6f4fa4f0b..2b4c4af98ccd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt @@ -30,4 +30,12 @@ constructor( value: Float, transitionState: TransitionState, ) : this(info.from, info.to, value, transitionState, info.ownerName) + + fun isTransitioning(from: KeyguardState? = null, to: KeyguardState? = null): Boolean { + return (from == null || this.from == from) && (to == null || this.to == to) + } + + fun isFinishedIn(state: KeyguardState): Boolean { + return to == state && transitionState == TransitionState.FINISHED + } } 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 4f00495819e8..1c7b4d996b36 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 @@ -22,6 +22,8 @@ import android.content.res.ColorStateList import android.util.StateSet import android.view.HapticFeedbackConstants import android.view.View +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb import androidx.core.view.isInvisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle @@ -61,6 +63,7 @@ object DeviceEntryIconViewBinder { bgViewModel: DeviceEntryBackgroundViewModel, falsingManager: FalsingManager, vibratorHelper: VibratorHelper, + overrideColor: Color? = null, ) { DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode() val longPressHandlingView = view.longPressHandlingView @@ -76,7 +79,7 @@ object DeviceEntryIconViewBinder { view, HapticFeedbackConstants.CONFIRM, ) - applicationScope.launch { viewModel.onLongPress() } + applicationScope.launch { viewModel.onUserInteraction() } } } @@ -116,6 +119,17 @@ object DeviceEntryIconViewBinder { launch("$TAG#viewModel.accessibilityDelegateHint") { viewModel.accessibilityDelegateHint.collect { hint -> view.accessibilityHintType = hint + if (hint != DeviceEntryIconView.AccessibilityHintType.NONE) { + view.setOnClickListener { + vibratorHelper.performHapticFeedback( + view, + HapticFeedbackConstants.CONFIRM, + ) + applicationScope.launch { viewModel.onUserInteraction() } + } + } else { + view.setOnClickListener(null) + } } } launch("$TAG#viewModel.useBackgroundProtection") { @@ -157,7 +171,8 @@ object DeviceEntryIconViewBinder { viewModel.type.contentDescriptionResId ) } - fgIconView.imageTintList = ColorStateList.valueOf(viewModel.tint) + fgIconView.imageTintList = + ColorStateList.valueOf(overrideColor?.toArgb() ?: viewModel.tint) fgIconView.setPadding( viewModel.padding, viewModel.padding, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt index ed5d53cc9371..c846cbe76770 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt @@ -33,14 +33,18 @@ import com.android.systemui.keyguard.shared.model.ClockSize import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel +import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.plugins.clocks.AodClockBurnInModel import com.android.systemui.plugins.clocks.ClockController +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch object KeyguardClockViewBinder { private val TAG = KeyguardClockViewBinder::class.simpleName!! // When changing to new clock, we need to remove old clock views from burnInLayer private var lastClock: ClockController? = null + @JvmStatic fun bind( clockSection: ClockSection, @@ -48,6 +52,7 @@ object KeyguardClockViewBinder { viewModel: KeyguardClockViewModel, keyguardClockInteractor: KeyguardClockInteractor, blueprintInteractor: KeyguardBlueprintInteractor, + rootViewModel: KeyguardRootViewModel, ) { keyguardRootView.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { @@ -105,6 +110,28 @@ object KeyguardClockViewBinder { } } } + + launch { + if (!MigrateClocksToBlueprint.isEnabled) return@launch + combine( + rootViewModel.translationX, + rootViewModel.translationY, + rootViewModel.scale, + ::Triple + ) + .collect { (translationX, translationY, scale) -> + viewModel.currentClock.value + ?.largeClock + ?.layout + ?.applyAodBurnIn( + AodClockBurnInModel( + translationX = translationX.value!!, + translationY = translationY, + scale = scale.scale + ) + ) + } + } } } } @@ -117,17 +144,16 @@ object KeyguardClockViewBinder { ) { val burnInLayer = viewModel.burnInLayer val clockController = viewModel.currentClock.value + // Large clocks won't be added to or removed from burn in layer + // Weather large clock has customized burn in preventing mechanism + // Non-weather large clock will only scale and translate vertically clockController?.let { clock -> when (clockSize) { ClockSize.LARGE -> { clock.smallClock.layout.views.forEach { burnInLayer?.removeView(it) } - if (clock.config.useAlternateSmartspaceAODTransition) { - clock.largeClock.layout.views.forEach { burnInLayer?.addView(it) } - } } ClockSize.SMALL -> { clock.smallClock.layout.views.forEach { burnInLayer?.addView(it) } - clock.largeClock.layout.views.forEach { burnInLayer?.removeView(it) } } } } @@ -148,13 +174,6 @@ object KeyguardClockViewBinder { burnInLayer?.removeView(it) rootView.removeView(it) } - - // add large clock to burn in layer only when it will have same transition with other - // components in AOD otherwise, it will have a separate scale transition while other - // components only have translate transition - if (clock.config.useAlternateSmartspaceAODTransition) { - clock.largeClock.layout.views.forEach { burnInLayer?.removeView(it) } - } clock.largeClock.layout.views.forEach { rootView.removeView(it) } } lastClock = currentClock 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 bda6438c308f..39db22d5616d 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 @@ -236,7 +236,8 @@ object KeyguardRootViewBinder { indicationArea, startButton, endButton, - lockIcon -> { + lockIcon, + deviceEntryIcon -> { // Do not move these views } else -> childView.translationX = px @@ -256,23 +257,6 @@ object KeyguardRootViewBinder { it.scaleX = scaleViewModel.scale it.scaleY = scaleViewModel.scale } - // Make sure to reset these views, or they will be invisible - if (childViews[burnInLayerId]?.scaleX != 1f) { - childViews[burnInLayerId]?.scaleX = 1f - childViews[burnInLayerId]?.scaleY = 1f - childViews[aodNotificationIconContainerId]?.scaleX = 1f - childViews[aodNotificationIconContainerId]?.scaleY = 1f - view.requestLayout() - } - } else { - // For weather clock, large clock should have only scale - // transition with other parts in burnInLayer - childViews[burnInLayerId]?.scaleX = scaleViewModel.scale - childViews[burnInLayerId]?.scaleY = scaleViewModel.scale - childViews[aodNotificationIconContainerId]?.scaleX = - scaleViewModel.scale - childViews[aodNotificationIconContainerId]?.scaleY = - scaleViewModel.scale } } } @@ -596,6 +580,7 @@ object KeyguardRootViewBinder { private val startButton = R.id.start_button private val endButton = R.id.end_button private val lockIcon = R.id.lock_icon_view + private val deviceEntryIcon = R.id.device_entry_icon_view private val nsslPlaceholderId = R.id.nssl_placeholder private const val ID = "occluding_app_device_entry_unlock_msg" 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 0f63f65e4511..1f4bc61fb003 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 @@ -29,7 +29,10 @@ import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionVi import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DozingToPrimaryBouncerTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DreamingToAodTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToOccludedTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.GoneToDozingTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.GoneToLockscreenTransitionViewModel @@ -40,6 +43,7 @@ import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGoneTransitionView import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.LockscreenToPrimaryBouncerTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.OccludedToAodTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.OccludedToGlanceableHubTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.OffToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToAodTransitionViewModel @@ -218,4 +222,28 @@ abstract class DeviceEntryIconTransitionModule { abstract fun primaryBouncerToLockscreen( impl: PrimaryBouncerToLockscreenTransitionViewModel ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun dreamingToGlanceableHub( + impl: DreamingToGlanceableHubTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun glanceableHubToDreaming( + impl: GlanceableHubToDreamingTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun glanceableHubToOccluded( + impl: GlanceableHubToOccludedTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet + abstract fun occludedToGlanceableHub( + impl: OccludedToGlanceableHubTransitionViewModel + ): DeviceEntryIconTransition } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt index 35b259849b78..200d30c66305 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt @@ -65,12 +65,12 @@ constructor( object : AccessibilityDelegate() { private val accessibilityAuthenticateHint = AccessibilityNodeInfo.AccessibilityAction( - AccessibilityNodeInfoCompat.ACTION_LONG_CLICK, + AccessibilityNodeInfoCompat.ACTION_CLICK, resources.getString(R.string.accessibility_authenticate_hint) ) private val accessibilityEnterHint = AccessibilityNodeInfo.AccessibilityAction( - AccessibilityNodeInfoCompat.ACTION_LONG_CLICK, + AccessibilityNodeInfoCompat.ACTION_CLICK, resources.getString(R.string.accessibility_enter_hint) ) override fun onInitializeAccessibilityNodeInfo( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt index ef2927096c04..b367715f529e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt @@ -38,6 +38,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.binder.KeyguardClockViewBinder 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.plugins.clocks.ClockController import com.android.systemui.plugins.clocks.ClockFaceLayout @@ -64,6 +65,7 @@ constructor( private val context: Context, val smartspaceViewModel: KeyguardSmartspaceViewModel, val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>, + private val rootViewModel: KeyguardRootViewModel, ) : KeyguardSection() { override fun addViews(constraintLayout: ConstraintLayout) {} override fun bindData(constraintLayout: ConstraintLayout) { @@ -75,7 +77,8 @@ constructor( constraintLayout, keyguardClockViewModel, clockInteractor, - blueprintInteractor.get() + blueprintInteractor.get(), + rootViewModel, ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt index 5b83a10c14c7..c05a1b732a50 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt @@ -27,7 +27,6 @@ import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.ComposeLockscreen import com.android.systemui.keyguard.shared.model.BurnInModel import com.android.systemui.keyguard.shared.model.ClockSize import com.android.systemui.keyguard.ui.StateToValue @@ -121,11 +120,13 @@ constructor( ), ) { interpolated, burnIn -> val useAltAod = - keyguardClockViewModel.currentClock.value?.let { clock -> - clock.config.useAlternateSmartspaceAODTransition - } == true + keyguardClockViewModel.currentClock.value + ?.config + ?.useAlternateSmartspaceAODTransition == true + // Only scale large non-weather clocks + // elements in large weather clock will translate the same as smartspace val useScaleOnly = - useAltAod && keyguardClockViewModel.clockSize.value == ClockSize.LARGE + (!useAltAod) && keyguardClockViewModel.clockSize.value == ClockSize.LARGE val burnInY = MathUtils.lerp(0, burnIn.translationY, interpolated).toInt() val translationY = @@ -134,35 +135,12 @@ constructor( } else { max(params.topInset, params.minViewY + burnInY) - params.minViewY } - if (ComposeLockscreen.isEnabled) { - BurnInModel( - translationX = MathUtils.lerp(0, burnIn.translationX, interpolated).toInt(), - translationY = translationY, - scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolated), - scaleClockOnly = !useScaleOnly, - ) - } else { - if (useScaleOnly) { - BurnInModel( - translationX = 0, - translationY = 0, - scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolated), - ) - } else { - // Ensure the desired translation doesn't encroach on the top inset - BurnInModel( - translationX = MathUtils.lerp(0, burnIn.translationX, interpolated).toInt(), - translationY = translationY, - scale = - MathUtils.lerp( - /* start= */ burnIn.scale, - /* stop= */ 1f, - /* amount= */ 1f - interpolated, - ), - scaleClockOnly = true, - ) - } - } + BurnInModel( + translationX = MathUtils.lerp(0, burnIn.translationX, interpolated).toInt(), + translationY = translationY, + scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolated), + scaleClockOnly = useScaleOnly + ) } } } 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 87324a233cef..6f8389fc8b7c 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 @@ -117,7 +117,8 @@ constructor( KeyguardState.DOZING, KeyguardState.DREAMING, KeyguardState.PRIMARY_BOUNCER, - KeyguardState.AOD -> emit(0f) + KeyguardState.AOD, + KeyguardState.UNDEFINED -> emit(0f) KeyguardState.ALTERNATE_BOUNCER, KeyguardState.LOCKSCREEN -> emit(1f) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt index da2fcc48a13d..ae83c9e720a3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel import android.animation.FloatEvaluator import android.animation.IntEvaluator import com.android.keyguard.KeyguardViewController +import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor @@ -68,6 +69,7 @@ constructor( private val keyguardViewController: Lazy<KeyguardViewController>, private val deviceEntryInteractor: DeviceEntryInteractor, private val deviceEntrySourceInteractor: DeviceEntrySourceInteractor, + private val accessibilityInteractor: AccessibilityInteractor, @Application private val scope: CoroutineScope, ) { val isUdfpsSupported: StateFlow<Boolean> = deviceEntryUdfpsInteractor.isUdfpsSupported @@ -146,10 +148,11 @@ constructor( KeyguardState.GLANCEABLE_HUB, KeyguardState.GONE, KeyguardState.OCCLUDED, - KeyguardState.DREAMING_LOCKSCREEN_HOSTED, -> 0f + KeyguardState.DREAMING_LOCKSCREEN_HOSTED, + KeyguardState.UNDEFINED, -> 0f KeyguardState.AOD, KeyguardState.ALTERNATE_BOUNCER, - KeyguardState.LOCKSCREEN -> 1f + KeyguardState.LOCKSCREEN, -> 1f } } val useBackgroundProtection: StateFlow<Boolean> = isUdfpsSupported @@ -232,7 +235,8 @@ constructor( } } val isVisible: Flow<Boolean> = deviceEntryViewAlpha.map { it > 0f }.distinctUntilChanged() - val isLongPressEnabled: Flow<Boolean> = + + private val isInteractive: Flow<Boolean> = combine( iconType, isUdfpsSupported, @@ -244,17 +248,24 @@ constructor( DeviceEntryIconView.IconType.NONE -> false } } - val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> = - combine(iconType, isLongPressEnabled) { deviceEntryStatus, longPressEnabled -> - if (longPressEnabled) { - deviceEntryStatus.toAccessibilityHintType() + accessibilityInteractor.isEnabled.flatMapLatest { touchExplorationEnabled -> + if (touchExplorationEnabled) { + combine(iconType, isInteractive) { iconType, isInteractive -> + if (isInteractive) { + iconType.toAccessibilityHintType() + } else { + DeviceEntryIconView.AccessibilityHintType.NONE + } + } } else { - DeviceEntryIconView.AccessibilityHintType.NONE + flowOf(DeviceEntryIconView.AccessibilityHintType.NONE) } } - suspend fun onLongPress() { + val isLongPressEnabled: Flow<Boolean> = isInteractive + + suspend fun onUserInteraction() { if (SceneContainerFlag.isEnabled) { deviceEntryInteractor.attemptDeviceEntry() } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt index 7468675c4bf8..a083c24e3d4d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt @@ -21,6 +21,7 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.res.R import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -37,7 +38,7 @@ class DreamingToGlanceableHubTransitionViewModel constructor( animationFlow: KeyguardTransitionAnimationFlow, configurationInteractor: ConfigurationInteractor, -) { +) : DeviceEntryIconTransition { private val transitionAnimation = animationFlow.setup( duration = TO_GLANCEABLE_HUB_DURATION, @@ -79,6 +80,15 @@ constructor( ) .map { step -> step != 0f } + override val deviceEntryParentViewAlpha: Flow<Float> = + transitionAnimation.sharedFlow( + startTime = 167.milliseconds, + duration = 167.milliseconds, + onStep = { it }, + onCancel = { 0f }, + onFinish = { 1f }, + ) + private companion object { val TO_GLANCEABLE_HUB_DURATION = 1.seconds } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt index 838c22b0da33..3716458079cd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt @@ -21,6 +21,7 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.res.R import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -37,7 +38,7 @@ class GlanceableHubToDreamingTransitionViewModel constructor( animationFlow: KeyguardTransitionAnimationFlow, configurationInteractor: ConfigurationInteractor, -) { +) : DeviceEntryIconTransition { private val transitionAnimation = animationFlow.setup( @@ -78,6 +79,14 @@ constructor( ) .map { step -> step != 1f } + override val deviceEntryParentViewAlpha: Flow<Float> = + transitionAnimation.sharedFlow( + duration = 167.milliseconds, + onStep = { 1 - it }, + onCancel = { 1f }, + onFinish = { 0f }, + ) + private companion object { val FROM_GLANCEABLE_HUB_DURATION = 1.seconds } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToOccludedTransitionViewModel.kt new file mode 100644 index 000000000000..300121facfd5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToOccludedTransitionViewModel.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromGlanceableHubTransitionInteractor.Companion.TO_OCCLUDED_DURATION +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +@SysUISingleton +class GlanceableHubToOccludedTransitionViewModel +@Inject +constructor( + animationFlow: KeyguardTransitionAnimationFlow, +) : DeviceEntryIconTransition { + + private val transitionAnimation = + animationFlow.setup( + duration = TO_OCCLUDED_DURATION, + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.OCCLUDED, + ) + + override val deviceEntryParentViewAlpha: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(0f) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt index bf225633fa86..e68e465ed55a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/MediaCarouselViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardMediaViewModel.kt @@ -16,10 +16,10 @@ package com.android.systemui.keyguard.ui.viewmodel -import com.android.systemui.media.controls.domain.pipeline.MediaDataManager +import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor import javax.inject.Inject +import kotlinx.coroutines.flow.StateFlow -class MediaCarouselViewModel @Inject constructor(private val mediaDataManager: MediaDataManager) { - val isMediaVisible: Boolean - get() = mediaDataManager.hasActiveMediaOrRecommendation() +class KeyguardMediaViewModel @Inject constructor(mediaCarouselInteractor: MediaCarouselInteractor) { + val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasActiveMediaOrRecommendation } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt index d8b50133949d..02e48fc5a09b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt @@ -28,6 +28,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel @@ -89,10 +90,15 @@ constructor( shadeMode: ShadeMode, ): Map<UserAction, UserActionResult> { val shadeSceneKey = - if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade + UserActionResult( + toScene = + if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade, + transitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split }, + ) val quickSettingsIfSingleShade = - if (shadeMode is ShadeMode.Single) Scenes.QuickSettings else shadeSceneKey + if (shadeMode is ShadeMode.Single) UserActionResult(Scenes.QuickSettings) + else shadeSceneKey return mapOf( Swipe.Left to UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable }, @@ -103,7 +109,7 @@ constructor( swipeDownFromTop(pointerCount = 2) to // TODO(b/338577208): Remove 'Dual' once we add Dual Shade invocation zones. if (shadeMode is ShadeMode.Dual) { - Scenes.QuickSettingsShade + UserActionResult(Scenes.QuickSettingsShade) } else { quickSettingsIfSingleShade }, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGlanceableHubTransitionViewModel.kt new file mode 100644 index 000000000000..73a4a9d4d2bb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGlanceableHubTransitionViewModel.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_GLANCEABLE_HUB_DURATION +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow + +@SysUISingleton +class OccludedToGlanceableHubTransitionViewModel +@Inject +constructor( + animationFlow: KeyguardTransitionAnimationFlow, +) : DeviceEntryIconTransition { + + private val transitionAnimation = + animationFlow.setup( + duration = TO_GLANCEABLE_HUB_DURATION, + from = KeyguardState.OCCLUDED, + to = KeyguardState.GLANCEABLE_HUB, + ) + + override val deviceEntryParentViewAlpha: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(1f) +} diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt index 9719c029574f..0c70f10bba21 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/data/repository/MediaFilterRepository.kt @@ -107,14 +107,11 @@ constructor( .thenByDescending { it.updateTime } .thenByDescending { it.notificationKey } - private val _sortedMedia: MutableStateFlow<TreeMap<MediaSortKeyModel, MediaCommonModel>> = - MutableStateFlow(TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator)) - val sortedMedia: StateFlow<Map<MediaSortKeyModel, MediaCommonModel>> = - _sortedMedia.asStateFlow() - - private val _isMediaFromRec: MutableStateFlow<Boolean> = MutableStateFlow(false) - val isMediaFromRec: StateFlow<Boolean> = _isMediaFromRec.asStateFlow() + private val _currentMedia: MutableStateFlow<List<MediaCommonModel>> = + MutableStateFlow(mutableListOf()) + val currentMedia = _currentMedia.asStateFlow() + private var sortedMedia = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator) private var mediaFromRecPackageName: String? = null private var locale: Locale = applicationContext.resources.configuration.locales.get(0) @@ -186,7 +183,7 @@ constructor( fun addMediaDataLoadingState(mediaDataLoadingModel: MediaDataLoadingModel) { val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator) sortedMap.putAll( - _sortedMedia.value.filter { (_, commonModel) -> + sortedMedia.filter { (_, commonModel) -> commonModel !is MediaCommonModel.MediaControl || commonModel.mediaLoadedModel.instanceId != mediaDataLoadingModel.instanceId } @@ -207,18 +204,52 @@ constructor( ) if (mediaDataLoadingModel is MediaDataLoadingModel.Loaded) { - val isMediaFromRec = isMediaFromRec(it) - - _isMediaFromRec.value = isMediaFromRec - if (isMediaFromRec) { - mediaFromRecPackageName = null + val newCommonModel = + MediaCommonModel.MediaControl( + mediaDataLoadingModel, + canBeRemoved(it), + isMediaFromRec(it) + ) + sortedMap[sortKey] = newCommonModel + + // On Addition or tapping on recommendations, we should show the new order of media. + if (mediaFromRecPackageName == it.packageName) { + if (it.isPlaying == true) { + mediaFromRecPackageName = null + _currentMedia.value = sortedMap.values.toList() + } + } else if (sortedMap.size > sortedMedia.size) { + _currentMedia.value = sortedMap.values.toList() + } else if (sortedMap.size == sortedMedia.size) { + // When loading an update for an existing media control. + val currentList = + mutableListOf<MediaCommonModel>().apply { addAll(_currentMedia.value) } + currentList.forEachIndexed { index, mediaCommonModel -> + if ( + mediaCommonModel is MediaCommonModel.MediaControl && + mediaCommonModel.mediaLoadedModel.instanceId == + mediaDataLoadingModel.instanceId && + mediaCommonModel != newCommonModel + ) { + // Update media model if changed. + currentList[index] = newCommonModel + } + } + _currentMedia.value = currentList } - sortedMap[sortKey] = - MediaCommonModel.MediaControl(mediaDataLoadingModel, canBeRemoved(it)) } } - _sortedMedia.value = sortedMap + sortedMedia = sortedMap + + // On removal we want to keep the order being shown to user. + if (mediaDataLoadingModel is MediaDataLoadingModel.Removed) { + _currentMedia.value = + _currentMedia.value.filter { commonModel -> + commonModel !is MediaCommonModel.MediaControl || + mediaDataLoadingModel.instanceId != commonModel.mediaLoadedModel.instanceId + } + } } fun setRecommendationsLoadingState(smartspaceMediaLoadingModel: SmartspaceMediaLoadingModel) { @@ -229,7 +260,7 @@ constructor( } val sortedMap = TreeMap<MediaSortKeyModel, MediaCommonModel>(comparator) sortedMap.putAll( - _sortedMedia.value.filter { (_, commonModel) -> + sortedMedia.filter { (_, commonModel) -> commonModel !is MediaCommonModel.MediaRecommendations } ) @@ -240,11 +271,25 @@ constructor( isPlaying = false, active = _smartspaceMediaData.value.isActive, ) - if (smartspaceMediaLoadingModel is SmartspaceMediaLoadingModel.Loaded) { - sortedMap[sortKey] = MediaCommonModel.MediaRecommendations(smartspaceMediaLoadingModel) + when (smartspaceMediaLoadingModel) { + is SmartspaceMediaLoadingModel.Loaded -> + sortedMap[sortKey] = + MediaCommonModel.MediaRecommendations(smartspaceMediaLoadingModel) + is SmartspaceMediaLoadingModel.Removed -> + _currentMedia.value = + _currentMedia.value.filter { commonModel -> + commonModel !is MediaCommonModel.MediaRecommendations + } } - _sortedMedia.value = sortedMap + if (sortedMap.size > sortedMedia.size) { + _currentMedia.value = sortedMap.values.toList() + } + sortedMedia = sortedMap + } + + fun setOrderedMedia() { + _currentMedia.value = sortedMedia.values.toList() } fun setMediaFromRecPackageName(packageName: String) { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt index c02478b02ec2..96ef7d250012 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImpl.kt @@ -206,11 +206,11 @@ constructor( listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) } } - override fun onMediaDataRemoved(key: String) { + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { allEntries.remove(key) userEntries.remove(key)?.let { // Only notify listeners if something actually changed - listeners.forEach { it.onMediaDataRemoved(key) } + listeners.forEach { it.onMediaDataRemoved(key, userInitiated) } } } @@ -246,7 +246,7 @@ constructor( // Only remove media when the profile is unavailable. if (DEBUG) Log.d(TAG, "Removing $key after profile change") userEntries.remove(key, data) - listeners.forEach { listener -> listener.onMediaDataRemoved(key) } + listeners.forEach { listener -> listener.onMediaDataRemoved(key, false) } } } } @@ -261,7 +261,7 @@ constructor( userEntries.clear() keyCopy.forEach { if (DEBUG) Log.d(TAG, "Removing $it after user change") - listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) } + listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it, false) } } allEntries.forEach { (key, data) -> 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 3a83115642bc..143d66b69f57 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 @@ -545,8 +545,8 @@ class LegacyMediaDataManagerImpl( * External listeners registered with [addListener] will be notified after the event propagates * through the internal listener pipeline. */ - private fun notifyMediaDataRemoved(key: String) { - internalListeners.forEach { it.onMediaDataRemoved(key) } + private fun notifyMediaDataRemoved(key: String, userInitiated: Boolean = false) { + internalListeners.forEach { it.onMediaDataRemoved(key, userInitiated) } } /** @@ -578,7 +578,7 @@ class LegacyMediaDataManagerImpl( if (it.active == !timedOut && !forceUpdate) { if (it.resumption) { if (DEBUG) Log.d(TAG, "timing out resume player $key") - dismissMediaData(key, 0L /* delay */) + dismissMediaData(key, delay = 0L, userInitiated = false) } return } @@ -627,17 +627,17 @@ class LegacyMediaDataManagerImpl( } } - private fun removeEntry(key: String, logEvent: Boolean = true) { + private fun removeEntry(key: String, logEvent: Boolean = true, userInitiated: Boolean = false) { mediaEntries.remove(key)?.let { if (logEvent) { logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId) } } - notifyMediaDataRemoved(key) + notifyMediaDataRemoved(key, userInitiated) } /** Dismiss a media entry. Returns false if the key was not found. */ - override fun dismissMediaData(key: String, delay: Long): Boolean { + override fun dismissMediaData(key: String, delay: Long, userInitiated: Boolean): Boolean { val existed = mediaEntries[key] != null backgroundExecutor.execute { mediaEntries[key]?.let { mediaData -> @@ -649,7 +649,10 @@ class LegacyMediaDataManagerImpl( } } } - foregroundExecutor.executeDelayed({ removeEntry(key) }, delay) + foregroundExecutor.executeDelayed( + { removeEntry(key = key, userInitiated = userInitiated) }, + delay + ) return existed } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt index ad70db5a3300..88910f944466 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatest.kt @@ -53,8 +53,8 @@ class MediaDataCombineLatest @Inject constructor() : listeners.toSet().forEach { it.onSmartspaceMediaDataLoaded(key, data) } } - override fun onMediaDataRemoved(key: String) { - remove(key) + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { + remove(key, userInitiated) } override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) { @@ -71,8 +71,8 @@ class MediaDataCombineLatest @Inject constructor() : } } - override fun onKeyRemoved(key: String) { - remove(key) + override fun onKeyRemoved(key: String, userInitiated: Boolean) { + remove(key, userInitiated) } /** @@ -92,10 +92,10 @@ class MediaDataCombineLatest @Inject constructor() : } } - private fun remove(key: String) { + private fun remove(key: String, userInitiated: Boolean) { entries.remove(key)?.let { val listenersCopy = listeners.toSet() - listenersCopy.forEach { it.onMediaDataRemoved(key) } + listenersCopy.forEach { it.onMediaDataRemoved(key, userInitiated) } } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt index 5432a189cf7c..8d19ce800cbe 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt @@ -213,7 +213,7 @@ constructor( listeners.forEach { it.onSmartspaceMediaDataLoaded(key, data, shouldPrioritizeMutable) } } - override fun onMediaDataRemoved(key: String) { + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { mediaFilterRepository.removeMediaEntry(key)?.let { mediaData -> val instanceId = mediaData.instanceId mediaFilterRepository.removeSelectedUserMediaEntry(instanceId)?.let { @@ -221,7 +221,7 @@ constructor( MediaDataLoadingModel.Removed(instanceId) ) // Only notify listeners if something actually changed - listeners.forEach { it.onMediaDataRemoved(key) } + listeners.forEach { it.onMediaDataRemoved(key, userInitiated) } } } } @@ -270,7 +270,7 @@ constructor( mediaFilterRepository.addMediaDataLoadingState( MediaDataLoadingModel.Removed(data.instanceId) ) - listeners.forEach { listener -> listener.onMediaDataRemoved(key) } + listeners.forEach { listener -> listener.onMediaDataRemoved(key, false) } } } } @@ -288,7 +288,7 @@ constructor( MediaDataLoadingModel.Removed(instanceId) ) getKey(instanceId)?.let { - listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) } + listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it, false) } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt index 2331aa2170ef..8099e593b33d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManager.kt @@ -60,7 +60,7 @@ interface MediaDataManager { ) /** Dismiss a media entry. Returns false if the key was not found. */ - fun dismissMediaData(key: String, delay: Long): Boolean + fun dismissMediaData(key: String, delay: Long, userInitiated: Boolean): Boolean /** * Called whenever the recommendation has been expired or removed by the user. This will remove @@ -136,7 +136,7 @@ interface MediaDataManager { ) {} /** Called whenever a previously existing Media notification was removed. */ - override fun onMediaDataRemoved(key: String) {} + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) {} /** * Called whenever a previously existing Smartspace media data was removed. 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 1d7c0256b2ef..eed775242d1f 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 @@ -498,8 +498,8 @@ class MediaDataProcessor( * External listeners registered with [MediaCarouselInteractor.addListener] will be notified * after the event propagates through the internal listener pipeline. */ - private fun notifyMediaDataRemoved(key: String) { - internalListeners.forEach { it.onMediaDataRemoved(key) } + private fun notifyMediaDataRemoved(key: String, userInitiated: Boolean = false) { + internalListeners.forEach { it.onMediaDataRemoved(key, userInitiated) } } /** @@ -531,7 +531,7 @@ class MediaDataProcessor( if (it.active == !timedOut && !forceUpdate) { if (it.resumption) { if (DEBUG) Log.d(TAG, "timing out resume player $key") - dismissMediaData(key, 0L /* delay */) + dismissMediaData(key, delayMs = 0L, userInitiated = false) } return } @@ -580,17 +580,17 @@ class MediaDataProcessor( } } - private fun removeEntry(key: String, logEvent: Boolean = true) { + private fun removeEntry(key: String, logEvent: Boolean = true, userInitiated: Boolean = false) { mediaDataRepository.removeMediaEntry(key)?.let { if (logEvent) { logger.logMediaRemoved(it.appUid, it.packageName, it.instanceId) } } - notifyMediaDataRemoved(key) + notifyMediaDataRemoved(key, userInitiated) } /** Dismiss a media entry. Returns false if the key was not found. */ - fun dismissMediaData(key: String, delayMs: Long): Boolean { + fun dismissMediaData(key: String, delayMs: Long, userInitiated: Boolean): Boolean { val existed = mediaDataRepository.mediaEntries.value[key] != null backgroundExecutor.execute { mediaDataRepository.mediaEntries.value[key]?.let { mediaData -> @@ -602,16 +602,19 @@ class MediaDataProcessor( } } } - foregroundExecutor.executeDelayed({ removeEntry(key) }, delayMs) + foregroundExecutor.executeDelayed( + { removeEntry(key, userInitiated = userInitiated) }, + delayMs + ) return existed } /** Dismiss a media entry. Returns false if the corresponding key was not found. */ - fun dismissMediaData(instanceId: InstanceId, delayMs: Long): Boolean { + fun dismissMediaData(instanceId: InstanceId, delayMs: Long, userInitiated: Boolean): Boolean { val mediaEntries = mediaDataRepository.mediaEntries.value val filteredEntries = mediaEntries.filter { (_, data) -> data.instanceId == instanceId } return if (filteredEntries.isNotEmpty()) { - dismissMediaData(filteredEntries.keys.first(), delayMs) + dismissMediaData(filteredEntries.keys.first(), delayMs, userInitiated) } else { false } @@ -1579,7 +1582,7 @@ class MediaDataProcessor( ) {} /** Called whenever a previously existing Media notification was removed. */ - fun onMediaDataRemoved(key: String) {} + fun onMediaDataRemoved(key: String, userInitiated: Boolean) {} /** * Called whenever a previously existing Smartspace media data was removed. 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 0e2814b3d91e..043fbfaa8a23 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 @@ -111,10 +111,10 @@ constructor( } } - override fun onMediaDataRemoved(key: String) { + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { val token = entries.remove(key) token?.stop() - token?.let { listeners.forEach { it.onKeyRemoved(key) } } + token?.let { listeners.forEach { it.onKeyRemoved(key, userInitiated) } } } fun dump(pw: PrintWriter) { @@ -136,7 +136,7 @@ constructor( /** Called when the route has changed for a given notification. */ fun onMediaDeviceChanged(key: String, oldKey: String?, data: MediaDeviceData?) /** Called when the notification was removed. */ - fun onKeyRemoved(key: String) + fun onKeyRemoved(key: String, userInitiated: Boolean) } private inner class Entry( diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt index b2a8f2e71cc6..b178d84c5d18 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilter.kt @@ -137,7 +137,7 @@ constructor( // farther and dismiss the media data so that media controls for the local session // don't hang around while casting. if (!keyedTokens.get(key)!!.contains(TokenId(remote.sessionToken))) { - dispatchMediaDataRemoved(key) + dispatchMediaDataRemoved(key, userInitiated = false) } } } @@ -151,11 +151,11 @@ constructor( backgroundExecutor.execute { dispatchSmartspaceMediaDataLoaded(key, data) } } - override fun onMediaDataRemoved(key: String) { + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { // Queue on background thread to ensure ordering of loaded and removed events is maintained. backgroundExecutor.execute { keyedTokens.remove(key) - dispatchMediaDataRemoved(key) + dispatchMediaDataRemoved(key, userInitiated) } } @@ -174,8 +174,10 @@ constructor( } } - private fun dispatchMediaDataRemoved(key: String) { - foregroundExecutor.execute { listeners.toSet().forEach { it.onMediaDataRemoved(key) } } + private fun dispatchMediaDataRemoved(key: String, userInitiated: Boolean) { + foregroundExecutor.execute { + listeners.toSet().forEach { it.onMediaDataRemoved(key, userInitiated) } + } } private fun dispatchSmartspaceMediaDataLoaded(key: String, info: SmartspaceMediaData) { 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 29f396700831..fc319036d67e 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 @@ -169,7 +169,7 @@ constructor( mediaListeners[key] = PlaybackStateListener(key, data) } - override fun onMediaDataRemoved(key: String) { + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { mediaListeners.remove(key)?.destroy() } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt index 33c0b195bf50..9e6230012760 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt @@ -63,7 +63,7 @@ constructor( private val mediaDeviceManager: MediaDeviceManager, private val mediaDataCombineLatest: MediaDataCombineLatest, private val mediaDataFilter: MediaDataFilterImpl, - mediaFilterRepository: MediaFilterRepository, + private val mediaFilterRepository: MediaFilterRepository, private val mediaFlags: MediaFlags, ) : MediaDataManager, CoreStartable { @@ -123,18 +123,8 @@ constructor( initialValue = false, ) - /** The most recent sorted set for user media instances */ - val sortedMedia: StateFlow<List<MediaCommonModel>> = - mediaFilterRepository.sortedMedia - .mapLatest { it.values.toList() } - .stateIn( - scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = emptyList(), - ) - - /** Whether the current change in media was done by clicking on a recommendation */ - val isMediaFromRec: StateFlow<Boolean> = mediaFilterRepository.isMediaFromRec + /** The current list for user media instances */ + val currentMedia: StateFlow<List<MediaCommonModel>> = mediaFilterRepository.currentMedia override fun start() { if (!mediaFlags.isMediaControlsRefactorEnabled()) { @@ -215,12 +205,12 @@ constructor( ) } - override fun dismissMediaData(key: String, delay: Long): Boolean { - return mediaDataProcessor.dismissMediaData(key, delay) + override fun dismissMediaData(key: String, delay: Long, userInitiated: Boolean): Boolean { + return mediaDataProcessor.dismissMediaData(key, delay, userInitiated) } fun removeMediaControl(instanceId: InstanceId, delay: Long) { - mediaDataProcessor.dismissMediaData(instanceId, delay) + mediaDataProcessor.dismissMediaData(instanceId, delay, userInitiated = false) } override fun dismissSmartspaceRecommendation(key: String, delay: Long) { @@ -251,6 +241,10 @@ constructor( override fun isRecommendationActive() = mediaDataRepository.smartspaceMediaData.value.isActive + fun reorderMedia() { + mediaFilterRepository.setOrderedMedia() + } + /** Add a listener for internal events. */ private fun addInternalListener(listener: MediaDataManager.Listener) = mediaDataProcessor.addInternalListener(listener) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt index 9f2d132fc7a9..d1fee903e6f5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaControlInteractor.kt @@ -90,7 +90,8 @@ constructor( instanceId: InstanceId, delayMs: Long ): Boolean { - val dismissed = mediaDataProcessor.dismissMediaData(instanceId, delayMs) + val dismissed = + mediaDataProcessor.dismissMediaData(instanceId, delayMs, userInitiated = true) if (!dismissed) { Log.w( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt index 23860bb9868c..56cc618eb61c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaCommonModel.kt @@ -21,6 +21,7 @@ sealed class MediaCommonModel { data class MediaControl( val mediaLoadedModel: MediaDataLoadingModel.Loaded, val canBeRemoved: Boolean = false, + val isMediaFromRec: Boolean = false, ) : MediaCommonModel() data class MediaRecommendations(val recsLoadingModel: SmartspaceMediaLoadingModel) : diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt index 0478178e71d6..b07253402645 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt @@ -193,6 +193,7 @@ constructor( private val mediaContent: ViewGroup @VisibleForTesting var pageIndicator: PageIndicator private var needsReordering: Boolean = false + private var isUserInitiatedRemovalQueued: Boolean = false private var keysNeedRemoval = mutableSetOf<String>() var shouldScrollToKey: Boolean = false private var isRtl: Boolean = false @@ -359,6 +360,8 @@ constructor( ) keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) mediaCarousel.repeatWhenAttached { + mediaCarouselViewModel.onAttached() + mediaCarouselScrollHandler.scrollToStart() repeatOnLifecycle(Lifecycle.State.STARTED) { listenForAnyStateToGoneKeyguardTransition(this) listenForAnyStateToLockscreenTransition(this) @@ -383,12 +386,15 @@ constructor( reorderAllPlayers(previousVisiblePlayerKey = null) } - keysNeedRemoval.forEach { removePlayer(it) } + keysNeedRemoval.forEach { + removePlayer(it, userInitiated = isUserInitiatedRemovalQueued) + } if (keysNeedRemoval.size > 0) { // Carousel visibility may need to be updated after late removals updateHostVisibility() } keysNeedRemoval.clear() + isUserInitiatedRemovalQueued = false // Update user visibility so that no extra impression will be logged when // activeMediaIndex resets to 0 @@ -472,18 +478,18 @@ constructor( val canRemove = data.isPlaying?.let { !it } ?: data.isClearable && !data.active if (canRemove && !Utils.useMediaResumption(context)) { - // This view isn't playing, let's remove this! This happens e.g. when - // dismissing/timing out a view. We still have the data around because - // resumption could be on, but we should save the resources and release - // this. + // This media control is both paused and timed out, and the resumption + // setting is off - let's remove it if (isReorderingAllowed) { - onMediaDataRemoved(key) + onMediaDataRemoved(key, userInitiated = MediaPlayerData.isSwipedAway) } else { + isUserInitiatedRemovalQueued = MediaPlayerData.isSwipedAway keysNeedRemoval.add(key) } } else { keysNeedRemoval.remove(key) } + MediaPlayerData.isSwipedAway = false } override fun onSmartspaceMediaDataLoaded( @@ -563,11 +569,12 @@ constructor( addSmartspaceMediaRecommendations(key, data, shouldPrioritize) } } + MediaPlayerData.isSwipedAway = false } - override fun onMediaDataRemoved(key: String) { - debugLogger.logMediaRemoved(key) - removePlayer(key) + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { + debugLogger.logMediaRemoved(key, userInitiated) + removePlayer(key, userInitiated = userInitiated) } override fun onSmartspaceMediaDataRemoved(key: String, immediately: Boolean) { @@ -734,6 +741,14 @@ constructor( viewController.setListening(mediaCarouselScrollHandler.visibleToUser && currentlyExpanded) updateViewControllerToState(viewController, noAnimation = true) updatePageIndicator() + if ( + commonViewModel is MediaCommonViewModel.MediaControl && commonViewModel.isMediaFromRec + ) { + mediaCarouselScrollHandler.scrollToPlayer( + mediaCarouselScrollHandler.visibleMediaIndex, + destIndex = 0 + ) + } mediaCarouselScrollHandler.onPlayersChanged() mediaFrame.requiresRemeasuring = true commonViewModel.onAdded(commonViewModel) @@ -1023,7 +1038,8 @@ constructor( fun removePlayer( key: String, dismissMediaData: Boolean = true, - dismissRecommendation: Boolean = true + dismissRecommendation: Boolean = true, + userInitiated: Boolean = false, ): MediaControlPanel? { if (key == MediaPlayerData.smartspaceMediaKey()) { MediaPlayerData.smartspaceMediaData?.let { @@ -1042,7 +1058,7 @@ constructor( if (dismissMediaData) { // Inform the media manager of a potentially late dismissal - mediaManager.dismissMediaData(key, delay = 0L) + mediaManager.dismissMediaData(key, delay = 0L, userInitiated = userInitiated) } if (dismissRecommendation) { // Inform the media manager of a potentially late dismissal @@ -1502,7 +1518,8 @@ constructor( } } - private fun onSwipeToDismiss() { + @VisibleForTesting + fun onSwipeToDismiss() { if (mediaFlags.isMediaControlsRefactorEnabled()) { mediaCarouselViewModel.onSwipeToDismiss() return @@ -1521,6 +1538,7 @@ constructor( it.mIsImpressed = false } } + MediaPlayerData.isSwipedAway = true logger.logSwipeDismiss() mediaManager.onSwipeToDismiss() } @@ -1547,6 +1565,7 @@ constructor( "state: ${desiredHostState?.expansion}, " + "only active ${desiredHostState?.showsOnlyActiveMedia}" ) + println("isSwipedAway: ${MediaPlayerData.isSwipedAway}") } } } @@ -1585,7 +1604,7 @@ internal object MediaPlayerData { val data: MediaData, val key: String, val updateTime: Long = 0, - val isSsReactivated: Boolean = false + val isSsReactivated: Boolean = false, ) private val comparator = @@ -1610,6 +1629,9 @@ internal object MediaPlayerData { // A map that tracks order of visible media players before they get reordered. private val visibleMediaPlayers = LinkedHashMap<String, MediaSortKey>() + // Whether the user swiped away the carousel since its last update + internal var isSwipedAway: Boolean = false + fun addMediaPlayer( key: String, data: MediaData, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt index ebf1c6a10703..1be25a74dbea 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLogger.kt @@ -53,8 +53,16 @@ constructor(@MediaCarouselControllerLog private val buffer: LogBuffer) { { "add player $str1, active: $bool1" } ) - fun logMediaRemoved(key: String) = - buffer.log(TAG, LogLevel.DEBUG, { str1 = key }, { "removing player $str1" }) + fun logMediaRemoved(key: String, userInitiated: Boolean) = + buffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = key + bool1 = userInitiated + }, + { "removing player $str1, by user $bool1" } + ) fun logRecommendationLoaded(key: String, isActive: Boolean) = buffer.log( diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java index e6c785ef41f0..0bc3c43993dd 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java @@ -786,10 +786,11 @@ public class MediaControlPanel { if (mKey != null) { closeGuts(); if (!mMediaDataManagerLazy.get().dismissMediaData(mKey, - MediaViewController.GUTS_ANIMATION_DURATION + 100)) { + /* delay */ MediaViewController.GUTS_ANIMATION_DURATION + 100, + /* userInitiated */ true)) { Log.w(TAG, "Manager failed to dismiss media " + mKey); // Remove directly from carousel so user isn't stuck with defunct controls - mMediaCarouselController.removePlayer(mKey, false, false); + mMediaCarouselController.removePlayer(mKey, false, false, true); } } else { Log.w(TAG, "Dismiss media with null notification. Token uid=" diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt index eca76b603b1a..91050c8bfab3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt @@ -105,7 +105,7 @@ class MediaHost( updateViewVisibility() } - override fun onMediaDataRemoved(key: String) { + override fun onMediaDataRemoved(key: String, userInitiated: Boolean) { updateViewVisibility() } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt index 96a8239a2c60..fd5f44594ae8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt @@ -26,21 +26,15 @@ import com.android.systemui.media.controls.domain.pipeline.interactor.factory.Me import com.android.systemui.media.controls.shared.model.MediaCommonModel import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.controls.util.MediaUiEventLogger -import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider import com.android.systemui.util.Utils -import com.android.systemui.util.kotlin.pairwiseBy -import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import java.util.concurrent.Executor import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn /** Models UI state and handles user inputs for media carousel */ @@ -60,45 +54,26 @@ constructor( private val mediaFlags: MediaFlags, ) { - @OptIn(ExperimentalCoroutinesApi::class) val mediaItems: StateFlow<List<MediaCommonViewModel>> = - conflatedCallbackFlow { - val listener = OnReorderingAllowedListener { trySend(Unit) } - visualStabilityProvider.addPersistentReorderingAllowedListener(listener) - trySend(Unit) - awaitClose { visualStabilityProvider.removeReorderingAllowedListener(listener) } - } - .flatMapLatest { - combine(interactor.isMediaFromRec, interactor.sortedMedia) { - isRecsToMedia, - sortedItems -> - buildList { - shouldReorder = isRecsToMedia - val reorderAllowed = isReorderingAllowed() - sortedItems.forEach { commonModel -> - if (!reorderAllowed || !modelsPendingRemoval.contains(commonModel)) { - when (commonModel) { - is MediaCommonModel.MediaControl -> - add(toViewModel(commonModel)) - is MediaCommonModel.MediaRecommendations -> - add(toViewModel(commonModel)) - } + interactor.currentMedia + .map { sortedItems -> + buildList { + sortedItems.forEach { commonModel -> + // When view is started we should make sure to clean models that are pending + // removal. + // This action should only be triggered once. + if (!isAttached || !modelsPendingRemoval.contains(commonModel)) { + when (commonModel) { + is MediaCommonModel.MediaControl -> add(toViewModel(commonModel)) + is MediaCommonModel.MediaRecommendations -> + add(toViewModel(commonModel)) } } - if (reorderAllowed) { - modelsPendingRemoval.clear() - } } - } - } - .pairwiseBy { old, new -> - // This condition can only happen when view is attached. So the old emit is of the - // most recent list updated. - // If the old list is empty, it is okay to emit the new ordered list. - if (isReorderingAllowed() || shouldReorder || old.isEmpty()) { - new - } else { - old + if (isAttached) { + modelsPendingRemoval.clear() + } + isAttached = false } } .stateIn( @@ -114,13 +89,18 @@ constructor( private var modelsPendingRemoval: MutableSet<MediaCommonModel> = mutableSetOf() - private var shouldReorder = true + private var isAttached = false fun onSwipeToDismiss() { logger.logSwipeDismiss() interactor.onSwipeToDismiss() } + fun onAttached() { + isAttached = true + interactor.reorderMedia() + } + private fun toViewModel( commonModel: MediaCommonModel.MediaControl ): MediaCommonViewModel.MediaControl { @@ -138,6 +118,7 @@ constructor( mediaControlByInstanceId.remove(instanceId) }, onUpdated = { onMediaControlAddedOrUpdated(it, commonModel) }, + isMediaFromRec = commonModel.isMediaFromRec ) .also { mediaControlByInstanceId[instanceId] = it } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt index aeaa82ed7244..a96d75c9ed30 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt @@ -32,6 +32,7 @@ sealed class MediaCommonViewModel { override val onAdded: (MediaCommonViewModel) -> Unit, override val onRemoved: (Boolean) -> Unit, override val onUpdated: (MediaCommonViewModel) -> Unit, + val isMediaFromRec: Boolean = false, ) : MediaCommonViewModel() data class MediaRecommendations( diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java index 88a5f78e407d..061e7ecfa4a3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java +++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamSentinel.java @@ -48,7 +48,7 @@ public class MediaDreamSentinel implements CoreStartable { } @Override - public void onMediaDataRemoved(@NonNull String key) { + public void onMediaDataRemoved(@NonNull String key, boolean userInitiated) { final boolean hasActiveMedia = mMediaDataManager.hasActiveMedia(); if (DEBUG) { Log.d(TAG, "onMediaDataRemoved(" + key + "), mAdded=" + mAdded + ", hasActiveMedia=" diff --git a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt index 89e4760615f0..a144dc2fc998 100644 --- a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt +++ b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt @@ -29,6 +29,7 @@ import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICA import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED +import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags import dagger.Lazy import javax.inject.Inject @@ -48,7 +49,7 @@ constructor( * Returns an override value for the given [flag] or `null` if the scene framework isn't enabled * or if the flag value doesn't need to be overridden. */ - fun flagValueOverride(flag: Int): Boolean? { + fun flagValueOverride(@SystemUiStateFlags flag: Long): Boolean? { if (!SceneContainerFlag.isEnabled) { return null } @@ -79,7 +80,7 @@ constructor( * to be overridden by the scene framework. */ val EvaluatorByFlag = - mapOf<Int, (SceneContainerPluginState) -> Boolean>( + mapOf<Long, (SceneContainerPluginState) -> Boolean>( SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to { it.scene != Scenes.Gone }, SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to { diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java index 2dd2327e5387..481b4761ccd9 100644 --- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java +++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java @@ -23,6 +23,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags; import dalvik.annotation.optimization.NeverCompile; @@ -42,10 +43,10 @@ public class SysUiState implements Dumpable { private final DisplayTracker mDisplayTracker; private final SceneContainerPlugin mSceneContainerPlugin; - private @QuickStepContract.SystemUiStateFlags int mFlags; + private @SystemUiStateFlags long mFlags; private final List<SysUiStateCallback> mCallbacks = new ArrayList<>(); - private int mFlagsToSet = 0; - private int mFlagsToClear = 0; + private long mFlagsToSet = 0; + private long mFlagsToClear = 0; public SysUiState(DisplayTracker displayTracker, SceneContainerPlugin sceneContainerPlugin) { mDisplayTracker = displayTracker; @@ -67,12 +68,13 @@ public class SysUiState implements Dumpable { } /** Returns the current sysui state flags. */ - public int getFlags() { + @SystemUiStateFlags + public long getFlags() { return mFlags; } /** Methods to this call can be chained together before calling {@link #commitUpdate(int)}. */ - public SysUiState setFlag(int flag, boolean enabled) { + public SysUiState setFlag(@SystemUiStateFlags long flag, boolean enabled) { final Boolean overrideOrNull = mSceneContainerPlugin.flagValueOverride(flag); if (overrideOrNull != null && enabled != overrideOrNull) { if (DEBUG) { @@ -91,7 +93,7 @@ public class SysUiState implements Dumpable { return this; } - /** Call to save all the flags updated from {@link #setFlag(int, boolean)}. */ + /** Call to save all the flags updated from {@link #setFlag(long, boolean)}. */ public void commitUpdate(int displayId) { updateFlags(displayId); mFlagsToSet = 0; @@ -105,14 +107,14 @@ public class SysUiState implements Dumpable { return; } - int newState = mFlags; + long newState = mFlags; newState |= mFlagsToSet; newState &= ~mFlagsToClear; notifyAndSetSystemUiStateChanged(newState, mFlags); } /** Notify all those who are registered that the state has changed. */ - private void notifyAndSetSystemUiStateChanged(int newFlags, int oldFlags) { + private void notifyAndSetSystemUiStateChanged(long newFlags, long oldFlags) { if (DEBUG) { Log.d(TAG, "SysUiState changed: old=" + oldFlags + " new=" + newFlags); } @@ -137,7 +139,7 @@ public class SysUiState implements Dumpable { /** Callback to be notified whenever system UI state flags are changed. */ public interface SysUiStateCallback{ /** To be called when any SysUiStateFlag gets updated */ - void onSystemUiStateChanged(@QuickStepContract.SystemUiStateFlags int sysUiFlags); + void onSystemUiStateChanged(@SystemUiStateFlags long sysUiFlags); } } diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt b/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt index 5c4915689f22..1e18f24c9e65 100644 --- a/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt +++ b/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt @@ -40,7 +40,7 @@ import com.android.systemui.dagger.qualifiers.DisplayId */ fun SysUiState.updateFlags( @DisplayId displayId: Int, - vararg flagValuePairs: Pair<Int, Boolean>, + vararg flagValuePairs: Pair<Long, Boolean>, ) { flagValuePairs.forEach { (flag, enabled) -> setFlag(flag, enabled) } commitUpdate(displayId) diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java index a6b6d61b464b..80c43796a55b 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java @@ -128,7 +128,7 @@ public final class NavBarHelper implements private boolean mLongPressHomeEnabled; private boolean mAssistantTouchGestureEnabled; private int mNavBarMode; - private int mA11yButtonState; + private long mA11yButtonState; private int mRotationWatcherRotation; private boolean mTogglingNavbarTaskbar; private boolean mWallpaperVisible; @@ -374,7 +374,7 @@ public final class NavBarHelper implements * {@link Secure#ACCESSIBILITY_BUTTON_MODE_GESTURE}, otherwise it is reset to 0. */ private void updateA11yState() { - final int prevState = mA11yButtonState; + final long prevState = mA11yButtonState; final boolean clickable; final boolean longClickable; if (mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode() @@ -431,7 +431,7 @@ public final class NavBarHelper implements * 48 = the combination of {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_CLICKABLE} and * {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE} */ - public int getA11yButtonState() { + public long getA11yButtonState() { return mA11yButtonState; } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 906ebad5dd8d..0e819c2b0a4f 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -1602,7 +1602,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements void updateAccessibilityStateFlags() { mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled(); if (mView != null) { - int a11yFlags = mNavBarHelper.getA11yButtonState(); + long a11yFlags = mNavBarHelper.getA11yButtonState(); boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0; boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0; mView.setAccessibilityButtonState(clickable, longClickable); @@ -1611,7 +1611,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } public void updateSystemUiStateFlags() { - int a11yFlags = mNavBarHelper.getA11yButtonState(); + long a11yFlags = mNavBarHelper.getA11yButtonState(); boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0; boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0; diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index f67973bcd70e..b360af098fa0 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -298,7 +298,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, } private void updateSysuiFlags() { - int a11yFlags = mNavBarHelper.getA11yButtonState(); + long a11yFlags = mNavBarHelper.getA11yButtonState(); boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0; boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0; diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 933065be4b0d..94870853f1be 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -84,6 +84,7 @@ import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.shared.system.InputMonitorCompat; import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -270,7 +271,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack private BackAnimation mBackAnimation; private int mLeftInset; private int mRightInset; - private int mSysUiFlags; + @SystemUiStateFlags + private long mSysUiFlags; // For Tf-Lite model. private BackGestureTfClassifierProvider mBackGestureTfClassifierProvider; @@ -334,7 +336,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack private final SysUiState.SysUiStateCallback mSysUiStateCallback = new SysUiState.SysUiStateCallback() { @Override - public void onSystemUiStateChanged(int sysUiFlags) { + public void onSystemUiStateChanged(@SystemUiStateFlags long sysUiFlags) { mSysUiFlags = sysUiFlags; } }; @@ -442,7 +444,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack | PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.GET_SHARED_LIBRARY_FILES)); int resId = resources.getIdentifier( - "gesture_blocking_activities", "array", recentsPackageName); + "back_gesture_blocking_activities", "array", recentsPackageName); if (resId == 0) { Log.e(TAG, "No resource found for gesture-blocking activities"); diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java index 6fb5174cc612..5720f7603ab2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java @@ -125,7 +125,10 @@ public class PageIndicator extends ViewGroup { public void setNumPages(int numPages) { setVisibility(numPages > 1 ? View.VISIBLE : View.GONE); - if (numPages == getChildCount()) { + int childCount = getChildCount(); + // We're checking if the width needs to be updated as it's possible that the number of pages + // was changed while the page indicator was not visible, automatically skipping onMeasure. + if (numPages == childCount && calculateWidth(childCount) == getMeasuredWidth()) { return; } if (mAnimating) { @@ -295,6 +298,10 @@ public class PageIndicator extends ViewGroup { } } + private int calculateWidth(int numPages) { + return (mPageIndicatorWidth - mPageDotWidth) * (numPages - 1) + mPageDotWidth; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int N = getChildCount(); @@ -309,7 +316,7 @@ public class PageIndicator extends ViewGroup { for (int i = 0; i < N; i++) { getChildAt(i).measure(widthChildSpec, heightChildSpec); } - int width = (mPageIndicatorWidth - mPageDotWidth) * (N - 1) + mPageDotWidth; + int width = calculateWidth(N); setMeasuredDimension(width, mPageIndicatorHeight); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index 2a726c2835d9..24b7a011f093 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -19,6 +19,8 @@ import static android.os.PowerWhitelistManager.REASON_TILE_ONCLICK; import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI; import static android.service.quicksettings.TileService.START_ACTIVITY_NEEDS_PENDING_INTENT; +import static com.android.systemui.Flags.qsCustomTileClickGuaranteedBugFix; + import android.app.ActivityManager; import android.app.compat.CompatChanges; import android.content.BroadcastReceiver; @@ -88,6 +90,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements private static final int MSG_ON_REMOVED = 1; private static final int MSG_ON_CLICK = 2; private static final int MSG_ON_UNLOCK_COMPLETE = 3; + private static final int MSG_ON_STOP_LISTENING = 4; // Bind retry control. private static final int MAX_BIND_RETRIES = 5; @@ -368,6 +371,16 @@ public class TileLifecycleManager extends BroadcastReceiver implements onUnlockComplete(); } } + if (qsCustomTileClickGuaranteedBugFix()) { + if (queue.contains(MSG_ON_STOP_LISTENING)) { + if (mDebug) Log.d(TAG, "Handling pending onStopListening " + getComponent()); + if (mListening) { + onStopListening(); + } else { + Log.w(TAG, "Trying to stop listening when not listening " + getComponent()); + } + } + } if (queue.contains(MSG_ON_REMOVED)) { if (mDebug) Log.d(TAG, "Handling pending onRemoved " + getComponent()); if (mListening) { @@ -586,10 +599,15 @@ public class TileLifecycleManager extends BroadcastReceiver implements @Override public void onStopListening() { - if (mDebug) Log.d(TAG, "onStopListening " + getComponent()); - mListening = false; - if (isNotNullAndFailedAction(mOptionalWrapper, QSTileServiceWrapper::onStopListening)) { - handleDeath(); + if (qsCustomTileClickGuaranteedBugFix() && hasPendingClick()) { + Log.d(TAG, "Enqueue stop listening"); + queueMessage(MSG_ON_STOP_LISTENING); + } else { + if (mDebug) Log.d(TAG, "onStopListening " + getComponent()); + mListening = false; + if (isNotNullAndFailedAction(mOptionalWrapper, QSTileServiceWrapper::onStopListening)) { + handleDeath(); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java index f8bf0a684506..6bc5095ed1ea 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java @@ -15,6 +15,8 @@ */ package com.android.systemui.qs.external; +import static com.android.systemui.Flags.qsCustomTileClickGuaranteedBugFix; + import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -37,6 +39,7 @@ import com.android.systemui.settings.UserTracker; import java.util.List; import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; /** * Manages the priority which lets {@link TileServices} make decisions about which tiles @@ -72,6 +75,8 @@ public class TileServiceManager { private boolean mPendingBind = true; private boolean mStarted = false; + private final AtomicBoolean mListeningFromRequest = new AtomicBoolean(false); + TileServiceManager(TileServices tileServices, Handler handler, ComponentName component, UserTracker userTracker, TileLifecycleManager.Factory tileLifecycleManagerFactory, CustomTileAddedRepository customTileAddedRepository) { @@ -159,15 +164,30 @@ public class TileServiceManager { } } + void onStartListeningFromRequest() { + mListeningFromRequest.set(true); + mStateManager.onStartListening(); + } + public void setLastUpdate(long lastUpdate) { mLastUpdate = lastUpdate; if (mBound && isActiveTile()) { - mStateManager.onStopListening(); - setBindRequested(false); + if (qsCustomTileClickGuaranteedBugFix()) { + if (mListeningFromRequest.compareAndSet(true, false)) { + stopListeningAndUnbind(); + } + } else { + stopListeningAndUnbind(); + } } mServices.recalculateBindAllowance(); } + private void stopListeningAndUnbind() { + mStateManager.onStopListening(); + setBindRequested(false); + } + public void handleDestroy() { setBindAllowed(false); mServices.getContext().unregisterReceiver(mUninstallReceiver); diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java index 8278c790226b..d457e88fcf14 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java @@ -15,6 +15,8 @@ */ package com.android.systemui.qs.external; +import static com.android.systemui.Flags.qsCustomTileClickGuaranteedBugFix; + import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; @@ -222,9 +224,13 @@ public class TileServices extends IQSService.Stub { return; } service.setBindRequested(true); - try { - service.getTileService().onStartListening(); - } catch (RemoteException e) { + if (qsCustomTileClickGuaranteedBugFix()) { + service.onStartListeningFromRequest(); + } else { + try { + service.getTileService().onStartListening(); + } catch (RemoteException e) { + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt index b515ce07cc02..278352c6f69b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt @@ -28,6 +28,7 @@ import com.android.systemui.log.ConstantStringsLoggerImpl import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel.DEBUG import com.android.systemui.log.core.LogLevel.ERROR +import com.android.systemui.log.core.LogLevel.INFO import com.android.systemui.log.core.LogLevel.VERBOSE import com.android.systemui.log.dagger.QSConfigLog import com.android.systemui.log.dagger.QSLog @@ -56,6 +57,9 @@ constructor( fun d(@CompileTimeConstant msg: String, arg: Any) { buffer.log(TAG, DEBUG, { str1 = arg.toString() }, { "$msg: $str1" }) } + fun i(@CompileTimeConstant msg: String, arg: Any) { + buffer.log(TAG, INFO, { str1 = arg.toString() }, { "$msg: $str1" }) + } fun logTileAdded(tileSpec: String) { buffer.log(TAG, DEBUG, { str1 = tileSpec }, { "[$str1] Tile added" }) diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt index e3ba36fe6e32..0696fbe996c0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/dagger/PanelsModule.kt @@ -16,8 +16,17 @@ package com.android.systemui.qs.panels.dagger +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogBufferFactory +import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository +import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepositoryImpl import com.android.systemui.qs.panels.data.repository.IconTilesRepository import com.android.systemui.qs.panels.data.repository.IconTilesRepositoryImpl +import com.android.systemui.qs.panels.domain.interactor.GridTypeConsistencyInteractor +import com.android.systemui.qs.panels.domain.interactor.InfiniteGridConsistencyInteractor +import com.android.systemui.qs.panels.domain.interactor.NoopGridConsistencyInteractor +import com.android.systemui.qs.panels.shared.model.GridConsistencyLog import com.android.systemui.qs.panels.shared.model.GridLayoutType import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType import com.android.systemui.qs.panels.ui.compose.GridLayout @@ -31,8 +40,23 @@ import dagger.multibindings.IntoSet interface PanelsModule { @Binds fun bindIconTilesRepository(impl: IconTilesRepositoryImpl): IconTilesRepository + @Binds + fun bindGridLayoutTypeRepository(impl: GridLayoutTypeRepositoryImpl): GridLayoutTypeRepository + + @Binds + fun bindDefaultGridConsistencyInteractor( + impl: NoopGridConsistencyInteractor + ): GridTypeConsistencyInteractor + companion object { @Provides + @SysUISingleton + @GridConsistencyLog + fun providesGridConsistencyLog(factory: LogBufferFactory): LogBuffer { + return factory.create("GridConsistencyLog", 50) + } + + @Provides @IntoSet fun provideGridLayout(gridLayout: InfiniteGridLayout): Pair<GridLayoutType, GridLayout> { return Pair(InfiniteGridLayoutType, gridLayout) @@ -44,5 +68,20 @@ interface PanelsModule { ): Map<GridLayoutType, GridLayout> { return entries.toMap() } + + @Provides + @IntoSet + fun provideGridConsistencyInteractor( + consistencyInteractor: InfiniteGridConsistencyInteractor + ): Pair<GridLayoutType, GridTypeConsistencyInteractor> { + return Pair(InfiniteGridLayoutType, consistencyInteractor) + } + + @Provides + fun provideGridConsistencyInteractorMap( + entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridTypeConsistencyInteractor>> + ): Map<GridLayoutType, GridTypeConsistencyInteractor> { + return entries.toMap() + } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt index 02dd33ebdbc2..542d0cbc425e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepository.kt @@ -20,10 +20,16 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.panels.shared.model.GridLayoutType import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +interface GridLayoutTypeRepository { + val layout: StateFlow<GridLayoutType> +} @SysUISingleton -class GridLayoutTypeRepository @Inject constructor() { - val layout: Flow<GridLayoutType> = flowOf(InfiniteGridLayoutType) +class GridLayoutTypeRepositoryImpl @Inject constructor() : GridLayoutTypeRepository { + private val _layout: MutableStateFlow<GridLayoutType> = MutableStateFlow(InfiniteGridLayoutType) + override val layout = _layout.asStateFlow() } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt index 92f87e78f090..e581bfceb18f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt @@ -19,20 +19,20 @@ package com.android.systemui.qs.panels.data.repository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.pipeline.shared.TileSpec import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow /** Repository for retrieving the list of [TileSpec] to be displayed as icons. */ interface IconTilesRepository { - val iconTilesSpecs: Flow<Set<TileSpec>> + val iconTilesSpecs: StateFlow<Set<TileSpec>> } @SysUISingleton class IconTilesRepositoryImpl @Inject constructor() : IconTilesRepository { - /** Set of toggleable tiles that are suitable for being shown as an icon. */ - override val iconTilesSpecs: Flow<Set<TileSpec>> = - flowOf( + private val _iconTilesSpecs = + MutableStateFlow( setOf( TileSpec.create("airplane"), TileSpec.create("battery"), @@ -50,4 +50,7 @@ class IconTilesRepositoryImpl @Inject constructor() : IconTilesRepository { TileSpec.create("rotation") ) ) + + /** Set of toggleable tiles that are suitable for being shown as an icon. */ + override val iconTilesSpecs: StateFlow<Set<TileSpec>> = _iconTilesSpecs.asStateFlow() } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepository.kt new file mode 100644 index 000000000000..43ccdf663478 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepository.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.data.repository + +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +@SysUISingleton +class InfiniteGridSizeRepository @Inject constructor() { + // Number of columns in the narrowest state for consistency + private val _columns = MutableStateFlow(4) + val columns: StateFlow<Int> = _columns.asStateFlow() +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt new file mode 100644 index 000000000000..7732092d6bcf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractor.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.qs.panels.shared.model.GridConsistencyLog +import com.android.systemui.qs.panels.shared.model.GridLayoutType +import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor +import com.android.systemui.qs.pipeline.shared.TileSpec +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +@SysUISingleton +class GridConsistencyInteractor +@Inject +constructor( + private val gridLayoutTypeInteractor: GridLayoutTypeInteractor, + private val currentTilesInteractor: CurrentTilesInteractor, + private val consistencyInteractors: + Map<GridLayoutType, @JvmSuppressWildcards GridTypeConsistencyInteractor>, + private val defaultConsistencyInteractor: GridTypeConsistencyInteractor, + @GridConsistencyLog private val logBuffer: LogBuffer, + @Application private val applicationScope: CoroutineScope, +) { + fun start() { + applicationScope.launch { + gridLayoutTypeInteractor.layout.collectLatest { type -> + val consistencyInteractor = + consistencyInteractors[type] ?: defaultConsistencyInteractor + currentTilesInteractor.currentTiles + .map { tiles -> tiles.map { it.spec } } + .collectLatest { tiles -> + val newTiles = consistencyInteractor.reconcileTiles(tiles) + if (newTiles != tiles) { + currentTilesInteractor.setTiles(newTiles) + logChange(newTiles) + } + } + } + } + } + + private fun logChange(tiles: List<TileSpec>) { + logBuffer.log( + LOG_BUFFER_CURRENT_TILES_CHANGE_TAG, + LogLevel.DEBUG, + { str1 = tiles.toString() }, + { "Tiles reordered: $str1" } + ) + } + + private companion object { + const val LOG_BUFFER_CURRENT_TILES_CHANGE_TAG = "GridConsistencyTilesChange" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt new file mode 100644 index 000000000000..4cdabaedc49e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridTypeConsistencyInteractor.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.domain.interactor + +import com.android.systemui.qs.pipeline.shared.TileSpec + +interface GridTypeConsistencyInteractor { + /** + * Given a list of tiles, return the best list of the same tiles (preserving as much order as + * possible, such that it's consistent with the current layout. + */ + fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt index 1aec193ca029..ccc1c6e9135c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt @@ -20,10 +20,10 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.panels.data.repository.IconTilesRepository import com.android.systemui.qs.pipeline.shared.TileSpec import javax.inject.Inject -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow /** Interactor for retrieving the list of [TileSpec] to be displayed as icons. */ @SysUISingleton class IconTilesInteractor @Inject constructor(repo: IconTilesRepository) { - val iconTilesSpecs: Flow<Set<TileSpec>> = repo.iconTilesSpecs + val iconTilesSpecs: StateFlow<Set<TileSpec>> = repo.iconTilesSpecs } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt new file mode 100644 index 000000000000..74e906c621cb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.domain.interactor + +import android.util.Log +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.pipeline.shared.TileSpec +import javax.inject.Inject + +@SysUISingleton +class InfiniteGridConsistencyInteractor +@Inject +constructor( + private val iconTilesInteractor: IconTilesInteractor, + private val gridSizeInteractor: InfiniteGridSizeInteractor +) : GridTypeConsistencyInteractor { + + /** + * Tries to fill in every columns of all rows (except the last row), potentially reordering + * tiles. + */ + override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> { + val newTiles: MutableList<TileSpec> = mutableListOf() + val row = TileRow(columns = gridSizeInteractor.columns.value) + val iconTilesSet = iconTilesInteractor.iconTilesSpecs.value + val tilesQueue = + ArrayDeque( + tiles.map { + SizedTile( + it, + width = + if (iconTilesSet.contains(it)) { + 1 + } else { + 2 + } + ) + } + ) + + while (tilesQueue.isNotEmpty()) { + if (row.isFull()) { + newTiles.addAll(row.tileSpecs()) + row.clear() + } + + val tile = tilesQueue.removeFirst() + + // If the tile fits in the row, add it. + if (!row.maybeAddTile(tile)) { + // If the tile does not fit the row, find an icon tile to move. + // We'll try to either add an icon tile from the queue to complete the row, or + // remove an icon tile from the current row to free up space. + + val iconTile: SizedTile? = tilesQueue.firstOrNull { it.width == 1 } + if (iconTile != null) { + tilesQueue.remove(iconTile) + tilesQueue.addFirst(tile) + row.maybeAddTile(iconTile) + } else { + val tileToRemove: SizedTile? = row.findLastIconTile() + if (tileToRemove != null) { + row.removeTile(tileToRemove) + row.maybeAddTile(tile) + + // Moving the icon tile to the end because there's no other + // icon tiles in the queue. + tilesQueue.addLast(tileToRemove) + } else { + // If the row does not have an icon tile, add the incomplete row. + // Note: this shouldn't happen because an icon tile is guaranteed to be in a + // row that doesn't have enough space for a large tile. + val tileSpecs = row.tileSpecs() + Log.wtf(TAG, "Uneven row does not have an icon tile to remove: $tileSpecs") + newTiles.addAll(tileSpecs) + row.clear() + tilesQueue.addFirst(tile) + } + } + } + } + + // Add last row that might be incomplete + newTiles.addAll(row.tileSpecs()) + + return newTiles.toList() + } + + /** Tile with a width representing the number of columns it should take. */ + private data class SizedTile(val spec: TileSpec, val width: Int) + + private class TileRow(private val columns: Int) { + private var availableColumns = columns + private val tiles: MutableList<SizedTile> = mutableListOf() + + fun tileSpecs(): List<TileSpec> { + return tiles.map { it.spec } + } + + fun maybeAddTile(tile: SizedTile): Boolean { + if (availableColumns - tile.width >= 0) { + tiles.add(tile) + availableColumns -= tile.width + return true + } + return false + } + + fun findLastIconTile(): SizedTile? { + return tiles.findLast { it.width == 1 } + } + + fun removeTile(tile: SizedTile) { + tiles.remove(tile) + availableColumns += tile.width + } + + fun clear() { + tiles.clear() + availableColumns = columns + } + + fun isFull(): Boolean = availableColumns == 0 + } + + private companion object { + const val TAG = "InfiniteGridConsistencyInteractor" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractor.kt new file mode 100644 index 000000000000..13c6072340c6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractor.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.panels.data.repository.InfiniteGridSizeRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.StateFlow + +@SysUISingleton +class InfiniteGridSizeInteractor @Inject constructor(repo: InfiniteGridSizeRepository) { + val columns: StateFlow<Int> = repo.columns +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt new file mode 100644 index 000000000000..0386a6ab20d6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractor.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.pipeline.shared.TileSpec +import javax.inject.Inject + +/** [GridTypeConsistencyInteractor] implementation that doesn't do any changes to tiles. */ +@SysUISingleton +class NoopGridConsistencyInteractor @Inject constructor() : GridTypeConsistencyInteractor { + override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> = tiles +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt new file mode 100644 index 000000000000..884cde35a1aa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.shared.model + +import javax.inject.Qualifier + +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class GridConsistencyLog() diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt index 6539cf35b073..dc43091e3c67 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt @@ -23,10 +23,12 @@ import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi import androidx.compose.animation.graphics.res.animatedVectorResource import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter import androidx.compose.animation.graphics.vector.AnimatedImageVector +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Box @@ -64,20 +66,20 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource -import androidx.compose.ui.res.integerResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.onClick import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.unit.dp -import com.android.compose.modifiers.background +import com.android.compose.animation.Expandable import com.android.compose.theme.colorAttr import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon import com.android.systemui.common.ui.compose.load import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor +import com.android.systemui.qs.panels.domain.interactor.InfiniteGridSizeInteractor import com.android.systemui.qs.panels.ui.viewmodel.ActiveTileColorAttributes import com.android.systemui.qs.panels.ui.viewmodel.AvailableEditActions import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel @@ -95,8 +97,12 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.mapLatest @SysUISingleton -class InfiniteGridLayout @Inject constructor(private val iconTilesInteractor: IconTilesInteractor) : - GridLayout { +class InfiniteGridLayout +@Inject +constructor( + private val iconTilesInteractor: IconTilesInteractor, + private val gridSizeInteractor: InfiniteGridSizeInteractor +) : GridLayout { private object TileType @@ -110,10 +116,10 @@ class InfiniteGridLayout @Inject constructor(private val iconTilesInteractor: Ic tiles.forEach { it.startListening(token) } onDispose { tiles.forEach { it.stopListening(token) } } } - val iconTilesSpecs by - iconTilesInteractor.iconTilesSpecs.collectAsState(initial = emptySet()) + val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsState() + val columns by gridSizeInteractor.columns.collectAsState() - TileLazyGrid(modifier) { + TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) { items( tiles.size, span = { index -> @@ -134,7 +140,7 @@ class InfiniteGridLayout @Inject constructor(private val iconTilesInteractor: Ic } } - @OptIn(ExperimentalCoroutinesApi::class) + @OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class) @Composable private fun Tile( tile: TileViewModel, @@ -147,28 +153,39 @@ class InfiniteGridLayout @Inject constructor(private val iconTilesInteractor: Ic .collectAsState(initial = tile.currentState.toUiState()) val context = LocalContext.current - Row( - modifier = modifier.clickable { tile.onClick(null) }.tileModifier(state.colors), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = tileHorizontalArrangement(iconOnly) + Expandable( + color = colorAttr(state.colors.background), + shape = RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius)), ) { - val icon = - remember(state.icon) { - state.icon.get().let { - if (it is QSTileImpl.ResourceIcon) { - Icon.Resource(it.resId, null) - } else { - Icon.Loaded(it.getDrawable(context), null) + Row( + modifier = + modifier + .combinedClickable( + onClick = { tile.onClick(it) }, + onLongClick = { tile.onLongClick(it) } + ) + .tileModifier(state.colors), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = tileHorizontalArrangement(iconOnly), + ) { + val icon = + remember(state.icon) { + state.icon.get().let { + if (it is QSTileImpl.ResourceIcon) { + Icon.Resource(it.resId, null) + } else { + Icon.Loaded(it.getDrawable(context), null) + } } } - } - TileContent( - label = state.label.toString(), - secondaryLabel = state.secondaryLabel.toString(), - icon = icon, - colors = state.colors, - iconOnly = iconOnly - ) + TileContent( + label = state.label.toString(), + secondaryLabel = state.secondaryLabel?.toString(), + icon = icon, + colors = state.colors, + iconOnly = iconOnly + ) + } } } @@ -187,8 +204,9 @@ class InfiniteGridLayout @Inject constructor(private val iconTilesInteractor: Ic val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsState(initial = emptySet()) val isIconOnly: (TileSpec) -> Boolean = remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } } + val columns by gridSizeInteractor.columns.collectAsState() - TileLazyGrid(modifier = modifier) { + TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) { // These Text are just placeholders to see the different sections. Not final UI. item(span = { GridItemSpan(maxLineSpan) }) { Text("Current tiles", color = Color.White) @@ -372,11 +390,11 @@ private fun TileIcon( @Composable private fun TileLazyGrid( modifier: Modifier = Modifier, + columns: GridCells, content: LazyGridScope.() -> Unit, ) { LazyVerticalGrid( - columns = - GridCells.Fixed(integerResource(R.integer.quick_settings_infinite_grid_num_columns)), + columns = columns, verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)), horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)), modifier = modifier, diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt index af1d195516e7..c8fbeb50b039 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt @@ -18,6 +18,8 @@ package com.android.systemui.qs.pipeline.domain.startable import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.flags.NewQsUI +import com.android.systemui.qs.panels.domain.interactor.GridConsistencyInteractor import com.android.systemui.qs.pipeline.domain.interactor.AccessibilityTilesInteractor import com.android.systemui.qs.pipeline.domain.interactor.AutoAddInteractor import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor @@ -34,6 +36,7 @@ constructor( private val autoAddInteractor: AutoAddInteractor, private val featureFlags: QSPipelineFlagsRepository, private val restoreReconciliationInteractor: RestoreReconciliationInteractor, + private val gridConsistencyInteractor: GridConsistencyInteractor, ) : CoreStartable { override fun start() { @@ -41,6 +44,10 @@ constructor( accessibilityTilesInteractor.init(currentTilesInteractor) autoAddInteractor.init(currentTilesInteractor) restoreReconciliationInteractor.start() + + if (NewQsUI.isEnabled) { + gridConsistencyInteractor.start() + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index c24113f14f00..56588ff75a5a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -55,6 +55,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtilsInternal; +import com.android.settingslib.graph.SignalDrawable; import com.android.systemui.Dumpable; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.Expandable; @@ -632,12 +633,23 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy } public static class DrawableIcon extends Icon { + protected final Drawable mDrawable; protected final Drawable mInvisibleDrawable; + private static final String TAG = "QSTileImpl"; public DrawableIcon(Drawable drawable) { mDrawable = drawable; - mInvisibleDrawable = drawable.getConstantState().newDrawable(); + Drawable.ConstantState nullableConstantState = drawable.getConstantState(); + if (nullableConstantState == null) { + if (!(drawable instanceof SignalDrawable)) { + Log.w(TAG, "DrawableIcon: drawable has null ConstantState" + + " and is not a SignalDrawable"); + } + mInvisibleDrawable = drawable; + } else { + mInvisibleDrawable = nullableConstantState.newDrawable(); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt index 065e89f10ef6..f0d72065397d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt @@ -175,6 +175,26 @@ constructor( ) } + /** Log with level [LogLevel.WARNING] */ + fun logWarning( + tileSpec: TileSpec, + message: String, + ) { + tileSpec + .getLogBuffer() + .log(tileSpec.getLogTag(), LogLevel.WARNING, { str1 = message }, { str1!! }) + } + + /** Log with level [LogLevel.INFO] */ + fun logInfo( + tileSpec: TileSpec, + message: String, + ) { + tileSpec + .getLogBuffer() + .log(tileSpec.getLogTag(), LogLevel.INFO, { str1 = message }, { str1!! }) + } + fun logCustomTileUserActionDelivered(tileSpec: TileSpec) { tileSpec .getLogBuffer() diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt index ffa3b543736b..4c210804b0c0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt @@ -25,10 +25,13 @@ import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor import com.android.systemui.qs.tiles.base.logging.QSTileLogger +import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent +import com.android.systemui.qs.tiles.impl.custom.di.QSTileConfigModule +import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import com.android.systemui.qs.tiles.impl.di.QSTileComponent -import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -47,7 +50,7 @@ sealed interface QSTileViewModelFactory<T> { * binding them together. This achieves a DI scope that lives along the instance of * [QSTileViewModelImpl]. */ - class Component<T> + class Component @Inject constructor( private val disabledByPolicyInteractor: DisabledByPolicyInteractor, @@ -58,7 +61,8 @@ sealed interface QSTileViewModelFactory<T> { private val qsTileConfigProvider: QSTileConfigProvider, private val systemClock: SystemClock, @Background private val backgroundDispatcher: CoroutineDispatcher, - ) : QSTileViewModelFactory<T> { + private val customTileComponentBuilder: CustomTileComponent.Builder, + ) : QSTileViewModelFactory<CustomTileDataModel> { /** * Creates [QSTileViewModelImpl] based on the interactors obtained from [QSTileComponent]. @@ -66,10 +70,10 @@ sealed interface QSTileViewModelFactory<T> { */ fun create( tileSpec: TileSpec, - componentFactory: (config: QSTileConfig) -> QSTileComponent<T> - ): QSTileViewModelImpl<T> { + ): QSTileViewModel { val config = qsTileConfigProvider.getConfig(tileSpec.spec) - val component = componentFactory(config) + val component = + customTileComponentBuilder.qsTileConfigModule(QSTileConfigModule(config)).build() return QSTileViewModelImpl( config, component::userActionInteractor, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt index 5122e1feabfc..f65fdb540527 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt @@ -22,9 +22,6 @@ import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory -import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent -import com.android.systemui.qs.tiles.impl.custom.di.QSTileConfigModule -import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel import com.android.systemui.qs.tiles.viewmodel.QSTileViewModelAdapter @@ -41,8 +38,7 @@ constructor( private val adapterFactory: QSTileViewModelAdapter.Factory, private val tileMap: Map<String, @JvmSuppressWildcards Provider<@JvmSuppressWildcards QSTileViewModel>>, - private val customTileComponentBuilder: CustomTileComponent.Builder, - private val customTileViewModelFactory: QSTileViewModelFactory.Component<CustomTileDataModel>, + private val customTileViewModelFactory: QSTileViewModelFactory.Component, ) : QSFactory { init { @@ -68,7 +64,5 @@ constructor( } private fun createCustomTileViewModel(spec: TileSpec.CustomTileSpec): QSTileViewModel = - customTileViewModelFactory.create(spec) { config -> - customTileComponentBuilder.qsTileConfigModule(QSTileConfigModule(config)).build() - } + customTileViewModelFactory.create(spec) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index 60469c070bf7..b057476ca194 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -16,6 +16,8 @@ package com.android.systemui.qs.tiles.dialog; +import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING; + import static com.android.settingslib.mobile.MobileMappings.getIconKey; import static com.android.settingslib.mobile.MobileMappings.mapIconSets; import static com.android.settingslib.wifi.WifiUtils.getHotspotIconResource; @@ -190,7 +192,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi private DialogTransitionAnimator mDialogTransitionAnimator; private boolean mHasWifiEntries; private WifiStateWorker mWifiStateWorker; - private boolean mHasActiveSubId; + private boolean mHasActiveSubIdOnDds; @VisibleForTesting static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f; @@ -298,7 +300,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi mExecutor); // Listen the subscription changes mOnSubscriptionsChangedListener = new InternetOnSubscriptionChangedListener(); - refreshHasActiveSubId(); + refreshHasActiveSubIdOnDds(); mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor, mOnSubscriptionsChangedListener); mDefaultDataSubId = getDefaultDataSubscriptionId(); @@ -428,7 +430,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi } boolean isActiveOnNonDds = getActiveAutoSwitchNonDdsSubId() != SubscriptionManager .INVALID_SUBSCRIPTION_ID; - if (!hasActiveSubId() || (!isVoiceStateInService(mDefaultDataSubId) + if (!hasActiveSubIdOnDds() || (!isVoiceStateInService(mDefaultDataSubId) && !isDataStateInService(mDefaultDataSubId) && !isActiveOnNonDds)) { if (DEBUG) { Log.d(TAG, "No carrier or service is out of service."); @@ -901,23 +903,42 @@ public class InternetDialogController implements AccessPointController.AccessPoi /** * @return whether there is the carrier item in the slice. */ - boolean hasActiveSubId() { + boolean hasActiveSubIdOnDds() { if (isAirplaneModeEnabled() || mTelephonyManager == null) { return false; } - return mHasActiveSubId; + return mHasActiveSubIdOnDds; + } + + private static boolean isEmbeddedSubscriptionVisible(@NonNull SubscriptionInfo subInfo) { + if (subInfo.isEmbedded() && subInfo.getProfileClass() == PROFILE_CLASS_PROVISIONING) { + return false; + } + return true; } - private void refreshHasActiveSubId() { + private void refreshHasActiveSubIdOnDds() { if (mSubscriptionManager == null) { - mHasActiveSubId = false; + mHasActiveSubIdOnDds = false; Log.e(TAG, "SubscriptionManager is null, set mHasActiveSubId = false"); return; } + int dds = getDefaultDataSubscriptionId(); + if (dds == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + mHasActiveSubIdOnDds = false; + Log.d(TAG, "DDS is INVALID_SUBSCRIPTION_ID"); + return; + } + SubscriptionInfo ddsSubInfo = mSubscriptionManager.getActiveSubscriptionInfo(dds); + if (ddsSubInfo == null) { + mHasActiveSubIdOnDds = false; + Log.e(TAG, "Can't get DDS subscriptionInfo"); + return; + } - mHasActiveSubId = mSubscriptionManager.getActiveSubscriptionIdList().length > 0; - Log.i(TAG, "mHasActiveSubId:" + mHasActiveSubId); + mHasActiveSubIdOnDds = isEmbeddedSubscriptionVisible(ddsSubInfo); + Log.i(TAG, "mHasActiveSubId:" + mHasActiveSubIdOnDds); } /** @@ -1209,7 +1230,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi @Override public void onSubscriptionsChanged() { - refreshHasActiveSubId(); + refreshHasActiveSubIdOnDds(); updateListener(); } } @@ -1306,6 +1327,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi Log.d(TAG, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED"); } mConfig = MobileMappings.Config.readConfig(context); + refreshHasActiveSubIdOnDds(); updateListener(); } else if (WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION.equals(action)) { updateListener(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java index 1a881b63720f..c9c44434361d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java @@ -429,7 +429,7 @@ public class InternetDialogDelegate implements } boolean isWifiEnabled = mInternetDialogController.isWifiEnabled(); - if (!mInternetDialogController.hasActiveSubId() + if (!mInternetDialogController.hasActiveSubIdOnDds() && (!isWifiEnabled || !isCarrierNetworkActive)) { mMobileNetworkLayout.setVisibility(View.GONE); if (mSecondaryMobileNetworkLayout != null) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt index d1c80309a1cc..bd2f2c987ccf 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileDataInteractor.kt @@ -17,15 +17,15 @@ package com.android.systemui.qs.tiles.impl.location.domain.interactor import android.os.UserHandle -import com.android.systemui.common.coroutine.ConflatedCallbackFlow import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel import com.android.systemui.statusbar.policy.LocationController +import com.android.systemui.util.kotlin.isLocationEnabledFlow import javax.inject.Inject -import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map /** Observes location state changes providing the [LocationTileModel]. */ class LocationTileDataInteractor @@ -38,19 +38,7 @@ constructor( user: UserHandle, triggers: Flow<DataUpdateTrigger> ): Flow<LocationTileModel> = - ConflatedCallbackFlow.conflatedCallbackFlow { - val initialValue = locationController.isLocationEnabled - trySend(LocationTileModel(initialValue)) - - val callback = - object : LocationController.LocationChangeCallback { - override fun onLocationSettingsChanged(locationEnabled: Boolean) { - trySend(LocationTileModel(locationEnabled)) - } - } - locationController.addCallback(callback) - awaitClose { locationController.removeCallback(callback) } - } + locationController.isLocationEnabledFlow().map { LocationTileModel(it) } override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt new file mode 100644 index 000000000000..88bd224881b5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileDataInteractor.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.night.domain.interactor + +import android.content.Context +import android.hardware.display.ColorDisplayManager +import android.os.UserHandle +import com.android.systemui.accessibility.data.repository.NightDisplayRepository +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor +import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel +import com.android.systemui.util.time.DateFormatUtil +import java.time.LocalTime +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map + +/** Observes screen record state changes providing the [NightDisplayTileModel]. */ +class NightDisplayTileDataInteractor +@Inject +constructor( + @Application private val context: Context, + private val dateFormatUtil: DateFormatUtil, + private val nightDisplayRepository: NightDisplayRepository, +) : QSTileDataInteractor<NightDisplayTileModel> { + + override fun tileData( + user: UserHandle, + triggers: Flow<DataUpdateTrigger> + ): Flow<NightDisplayTileModel> = + nightDisplayRepository.nightDisplayState(user).map { + generateModel( + it.autoMode, + it.isActivated, + it.startTime, + it.endTime, + it.shouldForceAutoMode, + it.locationEnabled + ) + } + + /** This checks resources and there fore does not make a binder call. */ + override fun availability(user: UserHandle): Flow<Boolean> = + flowOf(ColorDisplayManager.isNightDisplayAvailable(context)) + + private fun generateModel( + autoMode: Int, + isNightDisplayActivated: Boolean, + customStartTime: LocalTime?, + customEndTime: LocalTime?, + shouldForceAutoMode: Boolean, + locationEnabled: Boolean, + ): NightDisplayTileModel { + if (autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT) { + return NightDisplayTileModel.AutoModeTwilight( + isNightDisplayActivated, + shouldForceAutoMode, + locationEnabled, + ) + } else if (autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME) { + return NightDisplayTileModel.AutoModeCustom( + isNightDisplayActivated, + shouldForceAutoMode, + customStartTime, + customEndTime, + dateFormatUtil.is24HourFormat, + ) + } else { // auto mode off + return NightDisplayTileModel.AutoModeOff(isNightDisplayActivated, shouldForceAutoMode) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractor.kt new file mode 100644 index 000000000000..5cee8c49527d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/interactor/NightDisplayTileUserActionInteractor.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.night.domain.interactor + +import android.content.Intent +import android.hardware.display.ColorDisplayManager.AUTO_MODE_CUSTOM_TIME +import android.provider.Settings +import com.android.systemui.accessibility.data.repository.NightDisplayRepository +import com.android.systemui.accessibility.qs.QSAccessibilityModule +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.interactor.QSTileInput +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.base.logging.QSTileLogger +import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import javax.inject.Inject + +/** Handles night display tile clicks. */ +class NightDisplayTileUserActionInteractor +@Inject +constructor( + private val nightDisplayRepository: NightDisplayRepository, + private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler, + private val qsLogger: QSTileLogger, +) : QSTileUserActionInteractor<NightDisplayTileModel> { + override suspend fun handleInput(input: QSTileInput<NightDisplayTileModel>): Unit = + with(input) { + when (action) { + is QSTileUserAction.Click -> { + // Enroll in forced auto mode if eligible. + if (data.isEnrolledInForcedNightDisplayAutoMode) { + nightDisplayRepository.setNightDisplayAutoMode(AUTO_MODE_CUSTOM_TIME, user) + qsLogger.logInfo(spec, "Enrolled in forced night display auto mode") + } + nightDisplayRepository.setNightDisplayActivated(!data.isActivated, user) + } + is QSTileUserAction.LongClick -> { + qsTileIntentUserActionHandler.handle( + action.expandable, + Intent(Settings.ACTION_NIGHT_DISPLAY_SETTINGS) + ) + } + } + } + + companion object { + val spec = TileSpec.create(QSAccessibilityModule.NIGHT_DISPLAY_TILE_SPEC) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/model/NightDisplayTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/model/NightDisplayTileModel.kt new file mode 100644 index 000000000000..6b1bd5bc3512 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/domain/model/NightDisplayTileModel.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.qs.tiles.impl.night.domain.model + +import java.time.LocalTime + +/** Data model for night display tile */ +sealed interface NightDisplayTileModel { + val isActivated: Boolean + val isEnrolledInForcedNightDisplayAutoMode: Boolean + data class AutoModeTwilight( + override val isActivated: Boolean, + override val isEnrolledInForcedNightDisplayAutoMode: Boolean, + val isLocationEnabled: Boolean + ) : NightDisplayTileModel + data class AutoModeCustom( + override val isActivated: Boolean, + override val isEnrolledInForcedNightDisplayAutoMode: Boolean, + val startTime: LocalTime?, + val endTime: LocalTime?, + val is24HourFormat: Boolean + ) : NightDisplayTileModel + data class AutoModeOff( + override val isActivated: Boolean, + override val isEnrolledInForcedNightDisplayAutoMode: Boolean + ) : NightDisplayTileModel +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt new file mode 100644 index 000000000000..5c2dcfcaf37c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.night.ui + +import android.content.res.Resources +import android.service.quicksettings.Tile +import android.text.TextUtils +import androidx.annotation.StringRes +import com.android.systemui.accessibility.qs.QSAccessibilityModule +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.base.logging.QSTileLogger +import com.android.systemui.qs.tiles.impl.night.domain.model.NightDisplayTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import java.time.DateTimeException +import java.time.LocalTime +import java.time.format.DateTimeFormatter +import javax.inject.Inject + +/** Maps [NightDisplayTileModel] to [QSTileState]. */ +class NightDisplayTileMapper +@Inject +constructor( + @Main private val resources: Resources, + private val theme: Resources.Theme, + private val logger: QSTileLogger, +) : QSTileDataToStateMapper<NightDisplayTileModel> { + override fun map(config: QSTileConfig, data: NightDisplayTileModel): QSTileState = + QSTileState.build(resources, theme, config.uiConfig) { + label = resources.getString(R.string.quick_settings_night_display_label) + supportedActions = + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + sideViewIcon = QSTileState.SideViewIcon.None + + if (data.isActivated) { + activationState = QSTileState.ActivationState.ACTIVE + val loadedIcon = + Icon.Loaded( + resources.getDrawable(R.drawable.qs_nightlight_icon_on, theme), + contentDescription = null + ) + icon = { loadedIcon } + } else { + activationState = QSTileState.ActivationState.INACTIVE + val loadedIcon = + Icon.Loaded( + resources.getDrawable(R.drawable.qs_nightlight_icon_off, theme), + contentDescription = null + ) + icon = { loadedIcon } + } + + secondaryLabel = getSecondaryLabel(data, resources) + + contentDescription = + if (TextUtils.isEmpty(secondaryLabel)) label + else TextUtils.concat(label, ", ", secondaryLabel) + } + + private fun getSecondaryLabel( + data: NightDisplayTileModel, + resources: Resources + ): CharSequence? { + when (data) { + is NightDisplayTileModel.AutoModeTwilight -> { + if (!data.isLocationEnabled) { + return null + } else { + return resources.getString( + if (data.isActivated) + R.string.quick_settings_night_secondary_label_until_sunrise + else R.string.quick_settings_night_secondary_label_on_at_sunset + ) + } + } + is NightDisplayTileModel.AutoModeOff -> { + val subtitleArray = resources.getStringArray(R.array.tile_states_night) + return subtitleArray[ + if (data.isActivated) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE] + } + is NightDisplayTileModel.AutoModeCustom -> { + // User-specified time, approximated to the nearest hour. + @StringRes val toggleTimeStringRes: Int + val toggleTime: LocalTime + if (data.isActivated) { + toggleTime = data.endTime ?: return null + toggleTimeStringRes = R.string.quick_settings_secondary_label_until + } else { + toggleTime = data.startTime ?: return null + toggleTimeStringRes = R.string.quick_settings_night_secondary_label_on_at + } + + try { + val formatter = if (data.is24HourFormat) formatter24Hour else formatter12Hour + val formatArg = formatter.format(toggleTime) + return resources.getString(toggleTimeStringRes, formatArg) + } catch (exception: DateTimeException) { + logger.logWarning(spec, exception.message.toString()) + return null + } + } + } + } + + private companion object { + val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a") + val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm") + val spec = TileSpec.create(QSAccessibilityModule.NIGHT_DISPLAY_TILE_SPEC) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt index b88c1e566c4a..5346b237111f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt @@ -201,6 +201,7 @@ constructor( qsTileViewModel.currentState?.let { mapState(context, it, qsTileViewModel.config) } override fun getInstanceId(): InstanceId = qsTileViewModel.config.instanceId + override fun getTileLabel(): CharSequence = with(qsTileViewModel.config.uiConfig) { when (this) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt index 17698f9d8647..6cf2e52ff3d9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt @@ -29,7 +29,7 @@ import com.android.compose.animation.scene.UserActionResult import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor -import com.android.systemui.media.controls.domain.pipeline.MediaDataManager +import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor import com.android.systemui.qs.FooterActionsController import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.qs.ui.adapter.QSSceneAdapter @@ -63,7 +63,7 @@ constructor( private val footerActionsViewModelFactory: FooterActionsViewModel.Factory, private val footerActionsController: FooterActionsController, sceneBackInteractor: SceneBackInteractor, - val mediaDataManager: MediaDataManager, + val mediaCarouselInteractor: MediaCarouselInteractor, ) { private val backScene: StateFlow<SceneKey> = sceneBackInteractor.backScene @@ -101,6 +101,8 @@ constructor( ), ) + val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasAnyMediaOrRecommendation + private fun destinationScenes( isUnlocked: Boolean, canSwipeToDismiss: Boolean?, @@ -143,9 +145,4 @@ constructor( } return footerActionsViewModelFactory.create(lifecycleOwner) } - - fun isMediaVisible(): Boolean { - // TODO(b/328207006): use new pipeline to handle updates while visible - return mediaDataManager.hasAnyMediaOrRecommendation() - } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index faf2bbc1ba3b..e4cb211a430f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -108,6 +108,7 @@ import com.android.systemui.shade.shared.model.ShadeMode; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags; import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationShadeWindowController; @@ -117,6 +118,8 @@ import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder; import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.sysui.ShellInterface; +import dagger.Lazy; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -128,8 +131,6 @@ import java.util.function.Supplier; import javax.inject.Inject; import javax.inject.Provider; -import dagger.Lazy; - /** * Class to send information from overview to launcher with a binder. */ @@ -414,7 +415,13 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis @Override public void toggleNotificationPanel() { verifyCallerAndClearCallingIdentityPostMain("toggleNotificationPanel", () -> - mCommandQueue.togglePanel()); + mCommandQueue.toggleNotificationsPanel()); + } + + @Override + public void toggleQuickSettingsPanel() { + verifyCallerAndClearCallingIdentityPostMain("toggleQuickSettingsPanel", () -> + mCommandQueue.toggleQuickSettingsPanel()); } private boolean verifyCaller(String reason) { @@ -765,7 +772,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } - private void notifySystemUiStateFlags(int flags) { + private void notifySystemUiStateFlags(@SystemUiStateFlags long flags) { if (SysUiState.DEBUG) { Log.d(TAG_OPS, "Notifying sysui state change to overview service: proxy=" + mOverviewProxy + " flags=" + flags); diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt index eabc42b02665..3e2c6306467f 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt @@ -21,6 +21,7 @@ package com.android.systemui.scene.data.repository import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.SceneDataSource @@ -36,6 +37,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn +@SysUISingleton /** Source of truth for scene framework application state. */ class SceneContainerRepository @Inject diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index 08efe39d7674..0d0f6e069d2d 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -55,6 +55,18 @@ constructor( private val deviceUnlockedInteractor: DeviceUnlockedInteractor, ) { + interface OnSceneAboutToChangeListener { + + /** + * Notifies that the scene is about to change to [toScene]. + * + * The implementation can choose to consume the [sceneState] to prepare the incoming scene. + */ + fun onSceneAboutToChange(toScene: SceneKey, sceneState: Any?) + } + + private val onSceneAboutToChangeListener = mutableSetOf<OnSceneAboutToChangeListener>() + /** * The current scene. * @@ -149,6 +161,10 @@ constructor( return repository.allSceneKeys() } + fun registerSceneStateProcessor(processor: OnSceneAboutToChangeListener) { + onSceneAboutToChangeListener.add(processor) + } + /** * Requests a scene change to the given scene. * @@ -161,6 +177,7 @@ constructor( toScene: SceneKey, loggingReason: String, transitionKey: TransitionKey? = null, + sceneState: Any? = null, ) { val currentSceneKey = currentScene.value if ( @@ -180,6 +197,7 @@ constructor( isInstant = false, ) + onSceneAboutToChangeListener.forEach { it.onSceneAboutToChange(toScene, sceneState) } repository.changeScene(toScene, transitionKey) } 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 4a6427794def..3ce12dddb08e 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 @@ -234,7 +234,7 @@ constructor( bouncerInteractor.onImeHiddenByUser.collectLatest { if (sceneInteractor.currentScene.value == Scenes.Bouncer) { sceneInteractor.changeScene( - toScene = Scenes.Lockscreen, + toScene = Scenes.Lockscreen, // TODO(b/336581871): add sceneState? loggingReason = "IME hidden", ) } @@ -252,6 +252,7 @@ constructor( when { isAnySimLocked -> { switchToScene( + // TODO(b/336581871): add sceneState? targetSceneKey = Scenes.Bouncer, loggingReason = "Need to authenticate locked SIM card." ) @@ -259,6 +260,7 @@ constructor( unlockStatus.isUnlocked && deviceEntryInteractor.canSwipeToEnter.value == false -> { switchToScene( + // TODO(b/336581871): add sceneState? targetSceneKey = Scenes.Gone, loggingReason = "All SIM cards unlocked and device already unlocked and " + @@ -267,6 +269,7 @@ constructor( } else -> { switchToScene( + // TODO(b/336581871): add sceneState? targetSceneKey = Scenes.Lockscreen, loggingReason = "All SIM cards unlocked and device still locked" + @@ -325,7 +328,8 @@ constructor( Scenes.Gone to "device was unlocked in Bouncer scene" } else { val prevScene = previousScene.value - (prevScene ?: Scenes.Gone) to + (prevScene + ?: Scenes.Gone) to "device was unlocked in Bouncer scene, from sceneKey=$prevScene" } isOnLockscreen -> @@ -364,6 +368,7 @@ constructor( powerInteractor.isAsleep.collect { isAsleep -> if (isAsleep) { switchToScene( + // TODO(b/336581871): add sceneState? targetSceneKey = Scenes.Lockscreen, loggingReason = "device is starting to sleep", ) diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt index b91dd0451808..ef393e4858e2 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt @@ -24,6 +24,8 @@ import com.android.compose.animation.scene.TransitionKey * These are the subset of transitions that can be referenced by key when asking for a scene change. */ object TransitionKeys { + /** Reference to the gone/lockscreen to shade transition with split shade enabled. */ + val ToSplitShade = TransitionKey("GoneToSplitShade") /** Reference to a scene transition that can collapse the shade scene instantly. */ val CollapseShadeInstantly = TransitionKey("CollapseShadeInstantly") diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt index 259a8bfef175..b971781acd63 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt @@ -56,9 +56,8 @@ class SceneWindowRootView( } // TODO(b/298525212): remove once Compose exposes window inset bounds. - override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? { - val insets = super.onApplyWindowInsets(windowInsets) - this.windowInsets.value = insets - return insets + override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets { + this.windowInsets.value = windowInsets + return windowInsets } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt index 78704e16b586..c20d577d66a1 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt @@ -198,7 +198,7 @@ object SceneWindowRootViewBinder { private fun getDisplayWidth(context: Context): Dp { val point = Point() checkNotNull(context.display).getRealSize(point) - return point.x.dp + return point.x.toDp(context) } // TODO(b/298525212): remove once Compose exposes window inset bounds. diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt index b0af7f9ce072..99118bcfc322 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt @@ -24,6 +24,7 @@ import com.android.compose.animation.scene.UserActionResult import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode import javax.inject.Inject @@ -70,10 +71,11 @@ constructor( )] = UserActionResult(Scenes.QuickSettingsShade) } + val downSceneKey = + if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade + val downTransitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split } this[Swipe(direction = SwipeDirection.Down)] = - UserActionResult( - if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade - ) + UserActionResult(downSceneKey, downTransitionKey) } } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt index ef1d87da47be..a1dd4157d996 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt @@ -40,6 +40,7 @@ import dagger.assisted.AssistedInject */ interface ScreenshotActionsProvider { fun onScrollChipReady(onClick: Runnable) + fun onScrollChipInvalidated() fun setCompletedScreenshot(result: ScreenshotSavedResult) /** @@ -67,6 +68,8 @@ constructor( @Assisted val requestId: String, @Assisted val actionExecutor: ActionExecutor, ) : ScreenshotActionsProvider { + private var addedScrollChip = false + private var onScrollClick: Runnable? = null private var pendingAction: ((ScreenshotSavedResult) -> Unit)? = null private var result: ScreenshotSavedResult? = null @@ -122,18 +125,26 @@ constructor( } override fun onScrollChipReady(onClick: Runnable) { - viewModel.addAction( - ActionButtonAppearance( - AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_scroll), - context.resources.getString(R.string.screenshot_scroll_label), - context.resources.getString(R.string.screenshot_scroll_label), - ), - showDuringEntrance = true, - ) { - onClick.run() + onScrollClick = onClick + if (!addedScrollChip) { + viewModel.addAction( + ActionButtonAppearance( + AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_scroll), + context.resources.getString(R.string.screenshot_scroll_label), + context.resources.getString(R.string.screenshot_scroll_label), + ), + showDuringEntrance = true, + ) { + onScrollClick?.run() + } + addedScrollChip = true } } + override fun onScrollChipInvalidated() { + onScrollClick = null + } + override fun setCompletedScreenshot(result: ScreenshotSavedResult) { if (this.result != null) { Log.e(TAG, "Got a second completed screenshot for existing request!") diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index bd90de230d5f..2f026aee64d8 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -586,7 +586,11 @@ public class ScreenshotController { if (mConfigChanges.applyNewConfig(mContext.getResources())) { // Hide the scroll chip until we know it's available in this // orientation - mViewProxy.hideScrollChip(); + if (screenshotShelfUi2()) { + mActionsProvider.onScrollChipInvalidated(); + } else { + mViewProxy.hideScrollChip(); + } // Delay scroll capture eval a bit to allow the underlying activity // to set up in the new orientation. mScreenshotHandler.postDelayed( diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt index 412b08905ae9..1e66cd10cc61 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt @@ -86,7 +86,8 @@ constructor( viewModel, LayoutInflater.from(context), onDismissalRequested = { event, velocity -> requestDismissal(event, velocity) }, - onDismissalCancelled = { animationController.getSwipeReturnAnimation().start() } + onDismissalCancelled = { animationController.getSwipeReturnAnimation().start() }, + onUserInteraction = { callbacks?.onUserInteraction() } ) view.updateInsets(windowManager.currentWindowMetrics.windowInsets) addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt index 916d50fd75b1..969cf482be90 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt @@ -22,6 +22,8 @@ import android.graphics.Insets import android.graphics.Rect import android.graphics.Region import android.util.AttributeSet +import android.view.GestureDetector +import android.view.GestureDetector.SimpleOnGestureListener import android.view.MotionEvent import android.view.View import android.view.ViewGroup @@ -30,7 +32,6 @@ import android.widget.FrameLayout import android.widget.ImageView import com.android.systemui.res.R import com.android.systemui.screenshot.FloatingWindowUtil -import kotlin.math.max class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) { @@ -39,11 +40,50 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : private lateinit var screenshotStatic: ViewGroup var onTouchInterceptListener: ((MotionEvent) -> Boolean)? = null + var userInteractionCallback: (() -> Unit)? = null + private val displayMetrics = context.resources.displayMetrics private val tmpRect = Rect() private lateinit var actionsContainerBackground: View + private lateinit var actionsContainer: View private lateinit var dismissButton: View + // Prepare an internal `GestureDetector` to determine when we can initiate a touch-interception + // session (with the client's provided `onTouchInterceptListener`). We delegate out to their + // listener only for gestures that can't be handled by scrolling our `actionsContainer`. + private val gestureDetector = + GestureDetector( + context, + object : SimpleOnGestureListener() { + override fun onScroll( + ev1: MotionEvent?, + ev2: MotionEvent, + distanceX: Float, + distanceY: Float + ): Boolean { + actionsContainer.getBoundsOnScreen(tmpRect) + val touchedInActionsContainer = + tmpRect.contains(ev2.rawX.toInt(), ev2.rawY.toInt()) + val canHandleInternallyByScrolling = + touchedInActionsContainer + && actionsContainer.canScrollHorizontally(distanceX.toInt()) + return !canHandleInternallyByScrolling + } + } + ) + + init { + + // Delegate to the client-provided `onTouchInterceptListener` if we've already initiated + // touch-interception. + setOnTouchListener({ _: View, ev: MotionEvent -> + userInteractionCallback?.invoke() + onTouchInterceptListener?.invoke(ev) ?: false + }) + + gestureDetector.setIsLongpressEnabled(false) + } + override fun onFinishInflate() { super.onFinishInflate() // Get focus so that the key events go to the layout. @@ -52,7 +92,15 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : blurredScreenshotPreview = requireViewById(R.id.screenshot_preview_blur) screenshotStatic = requireViewById(R.id.screenshot_static) actionsContainerBackground = requireViewById(R.id.actions_container_background) + actionsContainer = requireViewById(R.id.actions_container) dismissButton = requireViewById(R.id.screenshot_dismiss_button) + + // Configure to extend the timeout during ongoing gestures (i.e. scrolls) that are already + // being handled by our child views. + actionsContainer.setOnTouchListener({ _: View, ev: MotionEvent -> + userInteractionCallback?.invoke() + false + }) } fun getTouchRegion(gestureInsets: Insets): Region { @@ -79,6 +127,18 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : val inPortrait = orientation == Configuration.ORIENTATION_PORTRAIT val cutout = insets.displayCutout val navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars()) + + // When honoring the navbar or other obstacle offsets, include some extra padding above + // the inset itself. + val verticalPadding = + mContext.resources.getDimensionPixelOffset(R.dimen.screenshot_shelf_vertical_margin) + + // Minimum bottom padding to always enforce (e.g. if there's no nav bar) + val minimumBottomPadding = + context.resources.getDimensionPixelOffset( + R.dimen.overlay_action_container_minimum_edge_spacing + ) + if (cutout == null) { screenshotStatic.setPadding(0, 0, 0, navBarInsets.bottom) } else { @@ -86,25 +146,41 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : if (inPortrait) { screenshotStatic.setPadding( waterfall.left, - max(cutout.safeInsetTop.toDouble(), waterfall.top.toDouble()).toInt(), + max(cutout.safeInsetTop, waterfall.top), waterfall.right, max( - cutout.safeInsetBottom.toDouble(), - max(navBarInsets.bottom.toDouble(), waterfall.bottom.toDouble()) - ) - .toInt() + navBarInsets.bottom + verticalPadding, + cutout.safeInsetBottom + verticalPadding, + waterfall.bottom + verticalPadding, + minimumBottomPadding, + ) ) } else { screenshotStatic.setPadding( - max(cutout.safeInsetLeft.toDouble(), waterfall.left.toDouble()).toInt(), + max(cutout.safeInsetLeft, waterfall.left), waterfall.top, - max(cutout.safeInsetRight.toDouble(), waterfall.right.toDouble()).toInt(), - max(navBarInsets.bottom.toDouble(), waterfall.bottom.toDouble()).toInt() + max(cutout.safeInsetRight, waterfall.right), + max( + navBarInsets.bottom + verticalPadding, + waterfall.bottom + verticalPadding, + minimumBottomPadding, + ) ) } } } + // Max function for two or more params. + private fun max(first: Int, second: Int, vararg items: Int): Int { + var largest = if (first > second) first else second + for (item in items) { + if (item > largest) { + largest = item + } + } + return largest + } + private fun getSwipeRegion(): Region { val swipeRegion = Region() val padding = FloatingWindowUtil.dpToPx(displayMetrics, -1 * TOUCH_PADDING_DP).toInt() @@ -127,10 +203,24 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : private const val TOUCH_PADDING_DP = 12f } + override fun onInterceptHoverEvent(event: MotionEvent): Boolean { + userInteractionCallback?.invoke() + return super.onInterceptHoverEvent(event) + } + override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { - if (onTouchInterceptListener?.invoke(ev) == true) { - return true + userInteractionCallback?.invoke() + + // Let the client-provided listener see all `DOWN` events so that they'll be able to + // interpret the remainder of the gesture, even if interception starts partway-through. + // TODO: is this really necessary? And if we don't go on to start interception, should we + // follow up with `ACTION_CANCEL`? + if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { + onTouchInterceptListener?.invoke(ev) } - return super.onInterceptTouchEvent(ev) + + // Only allow the client-provided touch interceptor to take over the gesture if our + // top-level `GestureDetector` decides not to scroll the action container. + return gestureDetector.onTouchEvent(ev) } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt index 750bd530d9b2..2243ade27b2e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt @@ -37,6 +37,14 @@ object ActionButtonViewBinder { // models would remove/create separate views. drawable?.setIcon(viewModel.appearance.icon) textView.text = viewModel.appearance.label + + viewModel.appearance.customBackground?.also { + if (it.canApplyTheme()) { + it.applyTheme(view.rootView.context.theme) + } + view.background = it + } + setMargins(iconView, textView, viewModel.appearance.label?.isNotEmpty() ?: false) if (viewModel.onClicked != null) { view.setOnClickListener { viewModel.onClicked.invoke() } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt index 43c0107c12d5..89f904a0878f 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt @@ -45,6 +45,7 @@ object ScreenshotShelfViewBinder { layoutInflater: LayoutInflater, onDismissalRequested: (event: ScreenshotEvent, velocity: Float?) -> Unit, onDismissalCancelled: () -> Unit, + onUserInteraction: () -> Unit ) { val swipeGestureListener = SwipeGestureListener( @@ -55,6 +56,7 @@ object ScreenshotShelfViewBinder { onCancel = onDismissalCancelled ) view.onTouchInterceptListener = { swipeGestureListener.onMotionEvent(it) } + view.userInteractionCallback = onUserInteraction val previewView: ImageView = view.requireViewById(R.id.screenshot_preview) val previewViewBlur: ImageView = view.requireViewById(R.id.screenshot_preview_blur) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt index 55a2ad21e292..2982ea011825 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonAppearance.kt @@ -19,8 +19,11 @@ package com.android.systemui.screenshot.ui.viewmodel import android.graphics.drawable.Drawable /** Data describing how an action should be shown to the user. */ -data class ActionButtonAppearance( +data class ActionButtonAppearance +@JvmOverloads +constructor( val icon: Drawable?, val label: CharSequence?, val description: CharSequence, + val customBackground: Drawable? = null, ) diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index 6367d44bd439..ee7b4beec64e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -40,6 +40,7 @@ import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent import com.android.systemui.communal.dagger.Communal import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.ui.compose.CommunalContainer +import com.android.systemui.communal.ui.compose.CommunalContent import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.util.CommunalColors import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -50,7 +51,6 @@ import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.SceneDataSourceDelegator import com.android.systemui.shade.domain.interactor.ShadeInteractor -import com.android.systemui.statusbar.phone.SystemUIDialogFactory import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf import com.android.systemui.util.kotlin.BooleanFlowOperators.not @@ -69,12 +69,12 @@ class GlanceableHubContainerController constructor( private val communalInteractor: CommunalInteractor, private val communalViewModel: CommunalViewModel, - private val dialogFactory: SystemUIDialogFactory, private val keyguardInteractor: KeyguardInteractor, private val shadeInteractor: ShadeInteractor, private val powerManager: PowerManager, private val communalColors: CommunalColors, private val ambientTouchComponentFactory: AmbientTouchComponent.Factory, + private val communalContent: CommunalContent, @Communal private val dataSourceDelegator: SceneDataSourceDelegator ) : LifecycleOwner { /** The container view for the hub. This will not be initialized until [initView] is called. */ @@ -180,7 +180,7 @@ constructor( viewModel = communalViewModel, colors = communalColors, dataSourceDelegator = dataSourceDelegator, - dialogFactory = dialogFactory, + content = communalContent, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt index d2c93da671af..884ccef3a080 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt @@ -140,7 +140,7 @@ constructor( private fun animateCollapseShadeInternal() { sceneInteractor.changeScene( - getCollapseDestinationScene(), + getCollapseDestinationScene(), // TODO(b/336581871): add sceneState? "ShadeController.animateCollapseShade", SlightlyFasterShadeCollapse, ) diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt index c9949cdc8ab6..55bd8c6c0834 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt @@ -44,6 +44,7 @@ constructor( } else { Scenes.Shade } + // TODO(b/336581871): add sceneState? sceneInteractor.changeScene(key, "animateCollapseQs") } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt index ac76becd1797..e4a2424e1ead 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt @@ -27,12 +27,13 @@ import com.android.compose.animation.scene.UserActionResult import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor -import com.android.systemui.media.controls.domain.pipeline.MediaDataManager +import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor import com.android.systemui.qs.FooterActionsController import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode @@ -60,7 +61,7 @@ constructor( val shadeHeaderViewModel: ShadeHeaderViewModel, val notifications: NotificationsPlaceholderViewModel, val brightnessMirrorViewModel: BrightnessMirrorViewModel, - val mediaDataManager: MediaDataManager, + val mediaCarouselInteractor: MediaCarouselInteractor, shadeInteractor: ShadeInteractor, private val footerActionsViewModelFactory: FooterActionsViewModel.Factory, private val footerActionsController: FooterActionsController, @@ -108,6 +109,8 @@ constructor( val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode + val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasActiveMediaOrRecommendation + /** * Amount of X-axis translation to apply to various elements as the unfolded foldable is folded * slightly, in pixels. @@ -125,11 +128,6 @@ constructor( sceneInteractor.changeScene(Scenes.Lockscreen, "Shade empty content clicked") } - fun isMediaVisible(): Boolean { - // TODO(b/296122467): handle updates to carousel visibility while scene is still visible - return mediaDataManager.hasActiveMediaOrRecommendation() - } - private val footerActionsControllerInitialized = AtomicBoolean(false) fun getFooterActionsViewModel(lifecycleOwner: LifecycleOwner): FooterActionsViewModel { @@ -152,11 +150,13 @@ constructor( else -> Scenes.Lockscreen } + val upTransitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split } + val down = Scenes.QuickSettings.takeIf { shadeMode is ShadeMode.Single } return buildMap { if (!isCustomizing) { - this[Swipe(SwipeDirection.Up)] = UserActionResult(up) + this[Swipe(SwipeDirection.Up)] = UserActionResult(up, upTransitionKey) } // TODO(b/330200163) Add an else to be able to collapse the shade while customizing down?.let { this[Swipe(SwipeDirection.Down)] = UserActionResult(down) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index d955349ffede..c9126161c40f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -134,7 +134,7 @@ public class CommandQueue extends IStatusBar.Stub implements private static final int MSG_DISMISS_KEYBOARD_SHORTCUTS = 32 << MSG_SHIFT; private static final int MSG_HANDLE_SYSTEM_KEY = 33 << MSG_SHIFT; private static final int MSG_SHOW_GLOBAL_ACTIONS = 34 << MSG_SHIFT; - private static final int MSG_TOGGLE_PANEL = 35 << MSG_SHIFT; + private static final int MSG_TOGGLE_NOTIFICATION_PANEL = 35 << MSG_SHIFT; private static final int MSG_SHOW_SHUTDOWN_UI = 36 << MSG_SHIFT; private static final int MSG_SET_TOP_APP_HIDES_STATUS_BAR = 37 << MSG_SHIFT; private static final int MSG_ROTATION_PROPOSAL = 38 << MSG_SHIFT; @@ -180,6 +180,7 @@ public class CommandQueue extends IStatusBar.Stub implements private static final int MSG_SET_QS_TILES = 79 << MSG_SHIFT; private static final int MSG_ENTER_DESKTOP = 80 << MSG_SHIFT; private static final int MSG_SET_SPLITSCREEN_FOCUS = 81 << MSG_SHIFT; + private static final int MSG_TOGGLE_QUICK_SETTINGS_PANEL = 82 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; public static final int FLAG_EXCLUDE_RECENTS_PANEL = 1 << 1; @@ -222,10 +223,33 @@ public class CommandQueue extends IStatusBar.Stub implements */ default void disable(int displayId, @DisableFlags int state1, @Disable2Flags int state2, boolean animate) { } + + /** + * Called to expand Notifications panel with animation. + */ default void animateExpandNotificationsPanel() { } + /** + * Called to collapse Notifications panel with animation. + * @param flags Exclusion flags. See {@link FLAG_EXCLUDE_NONE}. + * @param force True to force the operation. + */ default void animateCollapsePanels(int flags, boolean force) { } - default void togglePanel() { } - default void animateExpandSettingsPanel(String obj) { } + + /** + * Called to toggle Notifications panel. + */ + default void toggleNotificationsPanel() { } + + /** + * Called to expand Quick Settings panel with animation. + * @param subPanel subPanel one wish to expand. + */ + default void animateExpandSettingsPanel(String subPanel) { } + + /** + * Called to toggle Quick Settings panel. + */ + default void toggleQuickSettingsPanel() { } /** * Called to notify IME window status changes. @@ -696,10 +720,10 @@ public class CommandQueue extends IStatusBar.Stub implements } } - public void togglePanel() { + public void toggleNotificationsPanel() { synchronized (mLock) { - mHandler.removeMessages(MSG_TOGGLE_PANEL); - mHandler.obtainMessage(MSG_TOGGLE_PANEL, 0, 0).sendToTarget(); + mHandler.removeMessages(MSG_TOGGLE_NOTIFICATION_PANEL); + mHandler.obtainMessage(MSG_TOGGLE_NOTIFICATION_PANEL, 0, 0).sendToTarget(); } } @@ -710,6 +734,13 @@ public class CommandQueue extends IStatusBar.Stub implements } } + public void toggleQuickSettingsPanel() { + synchronized (mLock) { + mHandler.removeMessages(MSG_TOGGLE_QUICK_SETTINGS_PANEL); + mHandler.obtainMessage(MSG_TOGGLE_QUICK_SETTINGS_PANEL, 0, 0).sendToTarget(); + } + } + @Override public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, boolean showImeSwitcher) { @@ -1494,9 +1525,9 @@ public class CommandQueue extends IStatusBar.Stub implements mCallbacks.get(i).animateCollapsePanels(msg.arg1, msg.arg2 != 0); } break; - case MSG_TOGGLE_PANEL: + case MSG_TOGGLE_NOTIFICATION_PANEL: for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).togglePanel(); + mCallbacks.get(i).toggleNotificationsPanel(); } break; case MSG_EXPAND_SETTINGS: @@ -1504,6 +1535,11 @@ public class CommandQueue extends IStatusBar.Stub implements mCallbacks.get(i).animateExpandSettingsPanel((String) msg.obj); } break; + case MSG_TOGGLE_QUICK_SETTINGS_PANEL: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).toggleQuickSettingsPanel(); + } + break; case MSG_SHOW_IME_BUTTON: args = (SomeArgs) msg.obj; handleShowImeButton(args.argi1 /* displayId */, (IBinder) args.arg1 /* token */, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 7983db137e76..2446473c3b31 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -199,12 +199,14 @@ public class KeyguardIndicationController { protected boolean mPowerPluggedInWired; protected boolean mPowerPluggedInWireless; protected boolean mPowerPluggedInDock; + protected int mChargingSpeed; private boolean mPowerCharged; + /** Whether the battery defender is triggered. */ private boolean mBatteryDefender; + /** Whether the battery defender is triggered with the device plugged. */ private boolean mEnableBatteryDefender; private boolean mIncompatibleCharger; - protected int mChargingSpeed; private int mChargingWattage; private int mBatteryLevel; private boolean mBatteryPresent = true; @@ -1244,7 +1246,7 @@ public class KeyguardIndicationController { mChargingSpeed = status.getChargingSpeed(mContext); mBatteryLevel = status.level; mBatteryPresent = status.present; - mBatteryDefender = status.isBatteryDefender(); + mBatteryDefender = isBatteryDefender(status); // when the battery is overheated, device doesn't charge so only guard on pluggedIn: mEnableBatteryDefender = mBatteryDefender && status.isPluggedIn(); mIncompatibleCharger = status.incompatibleCharger.orElse(false); @@ -1516,6 +1518,11 @@ public class KeyguardIndicationController { return mPowerPluggedIn; } + /** Return true if the device is under the battery defender mode. */ + protected boolean isBatteryDefender(BatteryStatus status) { + return status.isBatteryDefender(); + } + private boolean isCurrentUser(int userId) { return getCurrentUser() == userId; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index d7d373226d5c..5bf2f41ae453 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -15,6 +15,8 @@ */ package com.android.systemui.statusbar; +import static com.android.systemui.Flags.mediaControlsUserInitiatedDismiss; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; @@ -175,14 +177,18 @@ public class NotificationMediaManager implements Dumpable { } @Override - public void onMediaDataRemoved(@NonNull String key) { + public void onMediaDataRemoved(@NonNull String key, boolean userInitiated) { + if (mediaControlsUserInitiatedDismiss() && !userInitiated) { + // Dismissing the notification will send the app's deleteIntent, so ignore if + // this was an automatic removal + Log.d(TAG, "Not dismissing " + key + " because it was removed by the system"); + return; + } mNotifPipeline.getAllNotifs() .stream() .filter(entry -> Objects.equals(entry.getKey(), key)) .findAny() .ifPresent(entry -> { - // TODO(b/160713608): "removing" this notification won't happen and - // won't send the 'deleteIntent' if the notification is ongoing. mNotifCollection.dismissNotification(entry, getDismissedByUserStats(entry)); }); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index 70632d5aa27a..79218ae4ca20 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar; import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD; import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_TO_AOD; +import static com.android.systemui.keyguard.shared.model.KeyguardState.GONE; import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows; import android.animation.Animator; @@ -49,6 +50,7 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteract import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus; import com.android.systemui.keyguard.MigrateClocksToBlueprint; import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.res.R; import com.android.systemui.scene.domain.interactor.SceneInteractor; @@ -108,6 +110,7 @@ public class StatusBarStateControllerImpl implements private final UiEventLogger mUiEventLogger; private final Lazy<InteractionJankMonitor> mInteractionJankMonitorLazy; private final JavaAdapter mJavaAdapter; + private final Lazy<KeyguardTransitionInteractor> mKeyguardTransitionInteractorLazy; private final Lazy<ShadeInteractor> mShadeInteractorLazy; private final Lazy<DeviceUnlockedInteractor> mDeviceUnlockedInteractorLazy; private final Lazy<SceneInteractor> mSceneInteractorLazy; @@ -175,6 +178,7 @@ public class StatusBarStateControllerImpl implements UiEventLogger uiEventLogger, Lazy<InteractionJankMonitor> interactionJankMonitorLazy, JavaAdapter javaAdapter, + Lazy<KeyguardTransitionInteractor> keyguardTransitionInteractor, Lazy<ShadeInteractor> shadeInteractorLazy, Lazy<DeviceUnlockedInteractor> deviceUnlockedInteractorLazy, Lazy<SceneInteractor> sceneInteractorLazy, @@ -182,6 +186,7 @@ public class StatusBarStateControllerImpl implements mUiEventLogger = uiEventLogger; mInteractionJankMonitorLazy = interactionJankMonitorLazy; mJavaAdapter = javaAdapter; + mKeyguardTransitionInteractorLazy = keyguardTransitionInteractor; mShadeInteractorLazy = shadeInteractorLazy; mDeviceUnlockedInteractorLazy = deviceUnlockedInteractorLazy; mSceneInteractorLazy = sceneInteractorLazy; @@ -193,6 +198,14 @@ public class StatusBarStateControllerImpl implements @Override public void start() { + mJavaAdapter.alwaysCollectFlow( + mKeyguardTransitionInteractorLazy.get().isFinishedInState(GONE), + (Boolean isFinishedInState) -> { + if (isFinishedInState) { + setLeaveOpenOnKeyguardHide(false); + } + }); + mJavaAdapter.alwaysCollectFlow(mShadeInteractorLazy.get().isAnyExpanded(), this::onShadeOrQsExpanded); 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 c17da4b3b4e6..05245898c161 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -32,6 +32,7 @@ import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpHandler; import com.android.systemui.dump.DumpManager; import com.android.systemui.media.controls.domain.pipeline.MediaDataManager; @@ -56,10 +57,10 @@ import com.android.systemui.statusbar.notification.collection.render.Notificatio import com.android.systemui.statusbar.phone.CentralSurfacesImpl; import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl; -import com.android.systemui.statusbar.phone.ui.StatusBarIconList; import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback; import com.android.systemui.statusbar.phone.ui.StatusBarIconController; import com.android.systemui.statusbar.phone.ui.StatusBarIconControllerImpl; +import com.android.systemui.statusbar.phone.ui.StatusBarIconList; import com.android.systemui.statusbar.policy.KeyguardStateController; import dagger.Binds; @@ -209,14 +210,16 @@ public interface CentralSurfacesDependenciesModule { /** */ @Provides @SysUISingleton - static ActivityTransitionAnimator provideActivityTransitionAnimator() { - return new ActivityTransitionAnimator(); + static ActivityTransitionAnimator provideActivityTransitionAnimator( + @Main Executor mainExecutor) { + return new ActivityTransitionAnimator(mainExecutor); } /** */ @Provides @SysUISingleton - static DialogTransitionAnimator provideDialogTransitionAnimator(IDreamManager dreamManager, + static DialogTransitionAnimator provideDialogTransitionAnimator(@Main Executor mainExecutor, + IDreamManager dreamManager, KeyguardStateController keyguardStateController, Lazy<AlternateBouncerInteractor> alternateBouncerInteractor, InteractionJankMonitor interactionJankMonitor, @@ -243,7 +246,7 @@ public interface CentralSurfacesDependenciesModule { } }; return new DialogTransitionAnimator( - callback, interactionJankMonitor, animationFeatureFlags); + mainExecutor, callback, interactionJankMonitor, animationFeatureFlags); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt index 1a223c110ad5..933eb207e76d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt @@ -18,12 +18,10 @@ package com.android.systemui.statusbar.notification.collection.coordinator -import android.os.SystemProperties import android.os.UserHandle import android.provider.Settings import androidx.annotation.VisibleForTesting import com.android.systemui.Dumpable -import com.android.systemui.Flags.notificationMinimalismPrototype import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dump.DumpManager @@ -41,6 +39,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider +import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.headsUpEvents import com.android.systemui.util.asIndenting @@ -264,7 +263,7 @@ constructor( } private fun unseenFeatureEnabled(): Flow<Boolean> { - if (notificationMinimalismPrototype()) { + if (NotificationMinimalismPrototype.V1.isEnabled) { return flowOf(true) } return secureSettings @@ -342,18 +341,6 @@ constructor( var hasFilteredAnyNotifs = false /** - * the [notificationMinimalismPrototype] will now show seen notifications on the locked - * shade by default, but this property read allows that to be quickly disabled for - * testing - */ - private val minimalismShowOnLockedShade - get() = - SystemProperties.getBoolean( - "persist.notification_minimalism_prototype.show_on_locked_shade", - true - ) - - /** * Encapsulates a definition of "being on the keyguard". Note that these two definitions * are wildly different: [StatusBarState.KEYGUARD] is when on the lock screen and does * not include shade or occluded states, whereas [KeyguardRepository.isKeyguardShowing] @@ -364,7 +351,10 @@ constructor( * allow seen notifications to appear in the locked shade. */ private fun isOnKeyguard(): Boolean = - if (notificationMinimalismPrototype() && minimalismShowOnLockedShade) { + if ( + NotificationMinimalismPrototype.V1.isEnabled && + NotificationMinimalismPrototype.V1.showOnLockedShade + ) { statusBarStateController.state == StatusBarState.KEYGUARD } else { keyguardRepository.isKeyguardShowing() 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 6a38f8df4715..d2d0aaa63003 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 @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.notification.row; import static com.android.systemui.Flags.notificationBackgroundTintOptimization; +import static com.android.systemui.statusbar.notification.row.ExpandableView.ClipSide.BOTTOM; +import static com.android.systemui.statusbar.notification.row.ExpandableView.ClipSide.TOP; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -43,6 +45,7 @@ import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.FakeShadowView; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.SourceType; +import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling; import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor; import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; @@ -354,12 +357,13 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView @Override public long performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, Runnable onStartedRunnable, Runnable onFinishedRunnable, - AnimatorListenerAdapter animationListener) { + AnimatorListenerAdapter animationListener, ClipSide clipSide) { enableAppearDrawing(true); mIsHeadsUpAnimation = isHeadsUpAnimation; if (mDrawingAppearAnimation) { startAppearAnimation(false /* isAppearing */, translationDirection, - delay, duration, onStartedRunnable, onFinishedRunnable, animationListener); + delay, duration, onStartedRunnable, onFinishedRunnable, animationListener, + clipSide); } else { if (onStartedRunnable != null) { onStartedRunnable.run(); @@ -378,13 +382,13 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mIsHeadsUpAnimation = isHeadsUpAppear; if (mDrawingAppearAnimation) { startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay, - duration, null, null, null); + duration, null, null, null, ClipSide.BOTTOM); } } private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay, long duration, final Runnable onStartedRunnable, final Runnable onFinishedRunnable, - AnimatorListenerAdapter animationListener) { + AnimatorListenerAdapter animationListener, ClipSide clipSide) { mAnimationTranslationY = translationDirection * getActualHeight(); cancelAppearAnimation(); if (mAppearAnimationFraction == -1.0f) { @@ -406,9 +410,16 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mCurrentAppearInterpolator = Interpolators.FAST_OUT_SLOW_IN_REVERSE; targetValue = 0.0f; } + + if (NotificationHeadsUpCycling.isEnabled()) { + // TODO(b/316404716): add avalanche filtering + mCurrentAppearInterpolator = Interpolators.LINEAR; + } + mAppearAnimator = ValueAnimator.ofFloat(mAppearAnimationFraction, targetValue); - if (NotificationsImprovedHunAnimation.isEnabled()) { + if (NotificationsImprovedHunAnimation.isEnabled() + || NotificationHeadsUpCycling.isEnabled()) { mAppearAnimator.setInterpolator(mCurrentAppearInterpolator); } else { mAppearAnimator.setInterpolator(Interpolators.LINEAR); @@ -418,7 +429,12 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mAppearAnimator.addUpdateListener(animation -> { mAppearAnimationFraction = (float) animation.getAnimatedValue(); updateAppearAnimationAlpha(); - updateAppearRect(); + if (NotificationHeadsUpCycling.isEnabled()) { + // For cycling out, we want the HUN to be clipped from the top. + updateAppearRect(clipSide); + } else { + updateAppearRect(); + } invalidate(); }); if (animationListener != null) { @@ -426,7 +442,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } // we need to apply the initial state already to avoid drawn frames in the wrong state updateAppearAnimationAlpha(); - updateAppearRect(); + if (NotificationHeadsUpCycling.isEnabled()) { + updateAppearRect(clipSide); + } else { + updateAppearRect(); + } mAppearAnimator.addListener(new AnimatorListenerAdapter() { private boolean mRunWithoutInterruptions; @@ -508,14 +528,18 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView enableAppearDrawing(false); } - private void updateAppearRect() { + /** + * Update the View's Rect clipping to fit the appear animation + * @param clipSide Which side if view we want to clip from + */ + private void updateAppearRect(ClipSide clipSide) { float interpolatedFraction = - NotificationsImprovedHunAnimation.isEnabled() ? mAppearAnimationFraction + NotificationsImprovedHunAnimation.isEnabled() + || NotificationHeadsUpCycling.isEnabled() ? mAppearAnimationFraction : mCurrentAppearInterpolator.getInterpolation(mAppearAnimationFraction); mAppearAnimationTranslation = (1.0f - interpolatedFraction) * mAnimationTranslationY; - final int actualHeight = getActualHeight(); - float bottom = actualHeight * interpolatedFraction; - + final int fullHeight = getActualHeight(); + float height = fullHeight * interpolatedFraction; if (mTargetPoint != null) { int width = getWidth(); float fraction = 1 - mAppearAnimationFraction; @@ -524,13 +548,26 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mAnimationTranslationY + (mAnimationTranslationY - mTargetPoint.y) * fraction, width - (width - mTargetPoint.x) * fraction, - actualHeight - (actualHeight - mTargetPoint.y) * fraction); + fullHeight - (fullHeight - mTargetPoint.y) * fraction); } else { - setOutlineRect(0, mAppearAnimationTranslation, getWidth(), - bottom + mAppearAnimationTranslation); + if (clipSide == TOP) { + setOutlineRect( + 0, + /* top= */ fullHeight - height, + getWidth(), + /* bottom= */ fullHeight + ); + } else if (clipSide == BOTTOM) { + setOutlineRect(0, mAppearAnimationTranslation, getWidth(), + height + mAppearAnimationTranslation); + } } } + private void updateAppearRect() { + updateAppearRect(ClipSide.BOTTOM); + } + private float getInterpolatedAppearAnimationFraction() { if (mAppearAnimationFraction >= 0) { @@ -540,11 +577,36 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } private void updateAppearAnimationAlpha() { - float contentAlphaProgress = MathUtils.constrain(mAppearAnimationFraction, - ALPHA_APPEAR_START_FRACTION, ALPHA_APPEAR_END_FRACTION); - float range = ALPHA_APPEAR_END_FRACTION - ALPHA_APPEAR_START_FRACTION; - float alpha = (contentAlphaProgress - ALPHA_APPEAR_START_FRACTION) / range; - setContentAlpha(Interpolators.ALPHA_IN.getInterpolation(alpha)); + updateAppearAnimationContentAlpha( + mAppearAnimationFraction, + ALPHA_APPEAR_START_FRACTION, + ALPHA_APPEAR_END_FRACTION, + Interpolators.ALPHA_IN + ); + } + + /** + * Update the alpha value of the content view during the appear animation. We suppose that the + * content alpha changes from 0 to 1 during some part of the appear animation. + * @param appearFraction the current appearFraction, should be in the range of [0, 1], where + * 1 represents fully appeared + * @param startFraction the appear fraction when the content view should be + * * fully transparent + * @param endFraction the appear fraction when the content view should be + * fully in-transparent, should be greater or equals to startFraction + * @param interpolator the interpolator to update the alpha + */ + private void updateAppearAnimationContentAlpha( + float appearFraction, + float startFraction, + float endFraction, + Interpolator interpolator + ) { + float contentAlphaProgress = MathUtils.constrain(appearFraction, startFraction, + endFraction); + float range = endFraction - startFraction; + float alpha = (contentAlphaProgress - startFraction) / range; + setContentAlpha(interpolator.getInterpolation(alpha)); } private void setContentAlpha(float contentAlpha) { 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 23c0a0db04d5..747cb3c8d934 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 @@ -3076,7 +3076,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView boolean isHeadsUpAnimation, Runnable onStartedRunnable, Runnable onFinishedRunnable, - AnimatorListenerAdapter animationListener) { + AnimatorListenerAdapter animationListener, ClipSide clipSide) { if (mMenuRow != null && mMenuRow.isMenuVisible()) { Animator anim = getTranslateViewAnimator(0f, null /* listener */); if (anim != null) { @@ -3092,7 +3092,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public void onAnimationEnd(Animator animation) { ExpandableNotificationRow.super.performRemoveAnimation( duration, delay, translationDirection, isHeadsUpAnimation, - null, onFinishedRunnable, animationListener); + null, onFinishedRunnable, animationListener, ClipSide.BOTTOM); } }); anim.start(); @@ -3100,7 +3100,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } return super.performRemoveAnimation(duration, delay, translationDirection, - isHeadsUpAnimation, onStartedRunnable, onFinishedRunnable, animationListener); + isHeadsUpAnimation, onStartedRunnable, onFinishedRunnable, animationListener, + clipSide); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 05e8717d0005..2af119f98f4a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -362,17 +362,17 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro /** * Perform a remove animation on this view. - * @param duration The duration of the remove animation. - * @param delay The delay of the animation + * + * @param duration The duration of the remove animation. + * @param delay The delay of the animation * @param translationDirection The direction value from [-1 ... 1] indicating in which the * animation should be performed. A value of -1 means that The * remove animation should be performed upwards, * such that the child appears to be going away to the top. 1 * Should mean the opposite. - * @param isHeadsUpAnimation Is this a headsUp animation. - * @param onFinishedRunnable A runnable which should be run when the animation is finished. - * @param animationListener An animation listener to add to the animation. - * + * @param isHeadsUpAnimation Is this a headsUp animation. + * @param onFinishedRunnable A runnable which should be run when the animation is finished. + * @param animationListener An animation listener to add to the animation. * @return The additional delay, in milliseconds, that this view needs to add before the * animation starts. */ @@ -380,7 +380,12 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro long delay, float translationDirection, boolean isHeadsUpAnimation, Runnable onStartedRunnable, Runnable onFinishedRunnable, - AnimatorListenerAdapter animationListener); + AnimatorListenerAdapter animationListener, ClipSide clipSide); + + public enum ClipSide { + TOP, + BOTTOM + } public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) { performAddAnimation(delay, duration, isHeadsUpAppear, null); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt index 816e5c132432..db3cf5abe618 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProvider.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.row import android.app.Flags +import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore import javax.inject.Inject /** @@ -27,11 +28,14 @@ interface HeadsUpStyleProvider { fun shouldApplyCompactStyle(): Boolean } -class HeadsUpStyleProviderImpl @Inject constructor() : HeadsUpStyleProvider { +class HeadsUpStyleProviderImpl +@Inject +constructor(private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore) : + HeadsUpStyleProvider { - /** - * TODO(b/270709257) This feature is under development. This method returns Compact when the - * flag is enabled for fish fooding purpose. - */ - override fun shouldApplyCompactStyle(): Boolean = Flags.compactHeadsUpNotification() + override fun shouldApplyCompactStyle(): Boolean { + // Use compact HUN for immersive mode. + return Flags.compactHeadsUpNotification() && + statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java index 162e8af47394..291dc132686b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java @@ -252,7 +252,7 @@ public abstract class StackScrollerDecorView extends ExpandableView { float translationDirection, boolean isHeadsUpAnimation, Runnable onStartedRunnable, Runnable onFinishedRunnable, - AnimatorListenerAdapter animationListener) { + AnimatorListenerAdapter animationListener, ClipSide clipSide) { // TODO: Use duration if (onStartedRunnable != null) { onStartedRunnable.run(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt index 0344b32dd6ad..d4f8ea385667 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt @@ -33,7 +33,12 @@ object NotificationHeadsUpCycling { /** Is the heads-up cycling animation enabled */ @JvmStatic inline val isEnabled - get() = Flags.notificationContentAlphaOptimization() + get() = Flags.notificationHeadsUpCycling() + + /** Whether to animate the bottom line when transiting from a tall HUN to a short HUN */ + @JvmStatic + inline val animateTallToShort + get() = false /** * Called to ensure code is only run when the flag is enabled. This protects users from the diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt new file mode 100644 index 000000000000..8889a10f5ad1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.shared + +import android.os.SystemProperties +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the minimalism prototype flag state. */ +@Suppress("NOTHING_TO_INLINE") +object NotificationMinimalismPrototype { + object V1 { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the heads-up cycling animation enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.notificationMinimalismPrototype() + + /** + * the prototype will now show seen notifications on the locked shade by default, but this + * property read allows that to be quickly disabled for testing + */ + val showOnLockedShade: Boolean + get() = + if (isUnexpectedlyInLegacyMode()) false + else + SystemProperties.getBoolean( + "persist.notification_minimalism_prototype.show_on_locked_shade", + true + ) + + /** gets the configurable max number of notifications */ + val maxNotifs: Int + get() = + if (isUnexpectedlyInLegacyMode()) -1 + else + SystemProperties.getInt( + "persist.notification_minimalism_prototype.lock_screen_max_notifs", + 1 + ) + + /** + * Called to ensure code is only run when the flag is enabled. This protects users from the + * unintended behaviors caused by accidentally running new logic, while also crashing on an + * eng build to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun isUnexpectedlyInLegacyMode() = + RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) + + /** + * Called to ensure code is only run when the flag is disabled. This will throw an exception + * if the flag is enabled to ensure that the refactor author catches issues in testing. + */ + @JvmStatic + inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index e520957975f3..5f4e832f31a3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -293,6 +293,8 @@ public class AmbientState implements Dumpable { } String getAvalancheShowingHunKey() { + // If we don't have a previous showing hun, we don't consider the showing hun as avalanche + if (isNullAvalancheKey(getAvalanchePreviousHunKey())) return ""; return mAvalancheController.getShowingHunKey(); } @@ -300,6 +302,11 @@ public class AmbientState implements Dumpable { return mAvalancheController.getPreviousHunKey(); } + boolean isNullAvalancheKey(String key) { + if (key == null || key.isEmpty()) return true; + return key.equals("HeadsUpEntry null") || key.equals("HeadsUpEntry.mEntry null"); + } + void setOverExpansion(float overExpansion) { mOverExpansion = overExpansion; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt index 5551ab46262c..bd7bd596438a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt @@ -70,13 +70,14 @@ class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableVie } override fun performRemoveAnimation( - duration: Long, - delay: Long, - translationDirection: Float, - isHeadsUpAnimation: Boolean, - onStartedRunnable: Runnable?, - onFinishedRunnable: Runnable?, - animationListener: AnimatorListenerAdapter? + duration: Long, + delay: Long, + translationDirection: Float, + isHeadsUpAnimation: Boolean, + onStartedRunnable: Runnable?, + onFinishedRunnable: Runnable?, + animationListener: AnimatorListenerAdapter?, + clipSide: ClipSide ): Long { return 0 } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 773a6bf752a6..abbe7d74733d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -112,6 +112,7 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; +import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling; import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor; import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation; import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor; @@ -127,6 +128,7 @@ import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.util.Assert; import com.android.systemui.util.ColorUtilKt; import com.android.systemui.util.DumpUtilsKt; +import com.android.systemui.util.ListenerSet; import com.google.errorprone.annotations.CompileTimeConstant; @@ -151,7 +153,6 @@ import java.util.function.Consumer; public class NotificationStackScrollLayout extends ViewGroup implements Dumpable, NotificationScrollView { - public static final float BACKGROUND_ALPHA_DIMMED = 0.7f; private static final String TAG = "StackScroller"; private static final boolean SPEW = Log.isLoggable(TAG, Log.VERBOSE); @@ -254,6 +255,7 @@ public class NotificationStackScrollLayout * The raw amount of the overScroll on the bottom, which is not rubber-banded. */ private float mOverScrolledBottomPixels; + private ListenerSet<Runnable> mStackHeightChangedListeners = new ListenerSet<>(); private NotificationLogger.OnChildLocationsChangedListener mListener; private OnNotificationLocationsChangedListener mLocationsChangedListener; private OnOverscrollTopChangedListener mOverscrollTopChangedListener; @@ -316,7 +318,7 @@ public class NotificationStackScrollLayout = new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { - if (SceneContainerFlag.isEnabled() && !mChildrenUpdateRequested) { + if (SceneContainerFlag.isEnabled()) { getViewTreeObserver().removeOnPreDrawListener(this); return true; } @@ -1083,6 +1085,10 @@ public class NotificationStackScrollLayout for (int i = 0; i < size; i++) { measureChild(getChildAt(i), childWidthSpec, childHeightSpec); } + if (SceneContainerFlag.isEnabled()) { + setMaxLayoutHeight(getMeasuredHeight()); + updateContentHeight(); + } Trace.endSection(); } @@ -1092,6 +1098,22 @@ public class NotificationStackScrollLayout super.requestLayout(); } + private void notifyStackHeightChangedListeners() { + for (Runnable listener : mStackHeightChangedListeners) { + listener.run(); + } + } + + @Override + public void addStackHeightChangedListener(@NonNull Runnable runnable) { + mStackHeightChangedListeners.addIfAbsent(runnable); + } + + @Override + public void removeStackHeightChangedListener(@NonNull Runnable runnable) { + mStackHeightChangedListeners.remove(runnable); + } + @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (!mSuppressChildrenMeasureAndLayout) { @@ -1110,8 +1132,10 @@ public class NotificationStackScrollLayout (int) height); } } - setMaxLayoutHeight(getHeight()); - updateContentHeight(); + if (!SceneContainerFlag.isEnabled()) { + setMaxLayoutHeight(getHeight()); + updateContentHeight(); + } clampScrollPosition(); requestChildrenUpdate(); updateFirstAndLastBackgroundViews(); @@ -1177,11 +1201,6 @@ public class NotificationStackScrollLayout } @Override - public void setStackHeightConsumer(@Nullable Consumer<Float> consumer) { - mScrollViewFields.setStackHeightConsumer(consumer); - } - - @Override public void setHeadsUpHeightConsumer(@Nullable Consumer<Float> consumer) { mScrollViewFields.setHeadsUpHeightConsumer(consumer); } @@ -1469,9 +1488,10 @@ public class NotificationStackScrollLayout public void setExpandedHeight(float height) { final boolean skipHeightUpdate = shouldSkipHeightUpdate(); - // when scene framework is enabled, updateStackPosition is already called by - // updateTopPadding every time the stack moves, so skip it here to avoid flickering. - if (!SceneContainerFlag.isEnabled()) { + // when scene framework is enabled and in single shade, updateStackPosition is already + // called by updateTopPadding every time the stack moves, so skip it here to avoid + // flickering. + if (!SceneContainerFlag.isEnabled() || mShouldUseSplitNotificationShade) { updateStackPosition(); } @@ -2403,16 +2423,25 @@ public class NotificationStackScrollLayout /* notificationStackScrollLayout= */ this, mMaxDisplayedNotifications, shelfIntrinsicHeight); mIntrinsicContentHeight = height; - mScrollViewFields.sendStackHeight(height + footerIntrinsicHeight); // The topPadding can be bigger than the regular padding when qs is expanded, in that // state the maxPanelHeight and the contentHeight should be bigger mContentHeight = (int) (height + Math.max(mIntrinsicPadding, getTopPadding()) + mBottomPadding); + mScrollViewFields.setIntrinsicStackHeight( + (int) (mIntrinsicPadding + mIntrinsicContentHeight + footerIntrinsicHeight + + mBottomPadding)); updateScrollability(); clampScrollPosition(); updateStackPosition(); mAmbientState.setContentHeight(mContentHeight); + + notifyStackHeightChangedListeners(); + } + + @Override + public int getIntrinsicStackHeight() { + return mScrollViewFields.getIntrinsicStackHeight(); } /** @@ -3143,6 +3172,11 @@ public class NotificationStackScrollLayout type = row.wasJustClicked() ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR; + if (NotificationHeadsUpCycling.isEnabled()) { + if (mStackScrollAlgorithm.isCyclingOut(row, mAmbientState)) { + type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_OUT; + } + } if (row.isChildInGroup()) { // We can otherwise get stuck in there if it was just isolated row.setHeadsUpAnimatingAway(false); @@ -3163,6 +3197,11 @@ public class NotificationStackScrollLayout if (pinnedAndClosed || shouldHunAppearFromTheBottom) { // Our custom add animation type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR; + if (NotificationHeadsUpCycling.isEnabled()) { + if (mStackScrollAlgorithm.isCyclingIn(row, mAmbientState)) { + type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_IN; + } + } } else { // Normal add animation type = AnimationEvent.ANIMATION_TYPE_ADD; @@ -3394,15 +3433,19 @@ public class NotificationStackScrollLayout int action = ev.getActionMasked(); boolean isUpOrCancel = action == ACTION_UP || action == ACTION_CANCEL; if (mSendingTouchesToSceneFramework) { - mController.sendTouchToSceneFramework(ev); + MotionEvent adjustedEvent = MotionEvent.obtain(ev); + adjustedEvent.setLocation(ev.getRawX(), ev.getRawY()); + mController.sendTouchToSceneFramework(adjustedEvent); mScrollViewFields.sendCurrentGestureOverscroll( getExpandedInThisMotion() && !isUpOrCancel); + adjustedEvent.recycle(); } else if (!isUpOrCancel) { // if this is the first touch being sent to the scene framework, // convert it into a synthetic DOWN event. mSendingTouchesToSceneFramework = true; MotionEvent downEvent = MotionEvent.obtain(ev); downEvent.setAction(MotionEvent.ACTION_DOWN); + downEvent.setLocation(ev.getRawX(), ev.getRawY()); mController.sendTouchToSceneFramework(downEvent); mScrollViewFields.sendCurrentGestureOverscroll(getExpandedInThisMotion()); downEvent.recycle(); @@ -6134,6 +6177,22 @@ public class NotificationStackScrollLayout .animateTopInset() .animateY() .animateZ(), + + // ANIMATION_TYPE_HEADS_UP_CYCLING_OUT + new AnimationFilter() + .animateHeight() + .animateTopInset() + .animateY() + .animateZ() + .hasDelays(), + + // ANIMATION_TYPE_HEADS_UP_CYCLING_IN + new AnimationFilter() + .animateHeight() + .animateTopInset() + .animateY() + .animateZ() + .hasDelays(), }; static int[] LENGTHS = new int[]{ @@ -6185,6 +6244,12 @@ public class NotificationStackScrollLayout // ANIMATION_TYPE_EVERYTHING StackStateAnimator.ANIMATION_DURATION_STANDARD, + + // ANIMATION_TYPE_HEADS_UP_CYCLING_OUT + StackStateAnimator.ANIMATION_DURATION_HEADS_UP_CYCLING, + + // ANIMATION_TYPE_HEADS_UP_CYCLING_IN + StackStateAnimator.ANIMATION_DURATION_HEADS_UP_CYCLING, }; static final int ANIMATION_TYPE_ADD = 0; @@ -6203,6 +6268,8 @@ public class NotificationStackScrollLayout static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 13; static final int ANIMATION_TYPE_HEADS_UP_OTHER = 14; static final int ANIMATION_TYPE_EVERYTHING = 15; + static final int ANIMATION_TYPE_HEADS_UP_CYCLING_OUT = 16; + static final int ANIMATION_TYPE_HEADS_UP_CYCLING_IN = 17; final long eventStartTime; final ExpandableView mChangingView; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt index 5bd4c758d678..a44674542b5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt @@ -17,11 +17,9 @@ package com.android.systemui.statusbar.notification.stack import android.content.res.Resources -import android.os.SystemProperties import android.util.Log import android.view.View.GONE import androidx.annotation.VisibleForTesting -import com.android.systemui.Flags.notificationMinimalismPrototype import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.controls.domain.pipeline.MediaDataManager @@ -31,6 +29,7 @@ import com.android.systemui.statusbar.StatusBarState.KEYGUARD import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView +import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype import com.android.systemui.statusbar.policy.SplitShadeStateController import com.android.systemui.util.Compile import com.android.systemui.util.children @@ -381,16 +380,13 @@ constructor( fun updateResources() { maxKeyguardNotifications = infiniteIfNegative( - if (notificationMinimalismPrototype()) { - SystemProperties.getInt( - "persist.notification_minimalism_prototype.lock_screen_max_notifs", - 1 - ) + if (NotificationMinimalismPrototype.V1.isEnabled) { + NotificationMinimalismPrototype.V1.maxNotifs } else { resources.getInteger(R.integer.keyguard_max_notification_count) } ) - maxNotificationsExcludesMedia = notificationMinimalismPrototype() + maxNotificationsExcludesMedia = NotificationMinimalismPrototype.V1.isEnabled dividerHeight = max(1f, resources.getDimensionPixelSize(R.dimen.notification_divider_height).toFloat()) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt index a3827c140214..6afcf372b039 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt @@ -45,6 +45,12 @@ class ScrollViewFields { var isScrolledToTop: Boolean = true /** + * Height in view pixels at which the Notification Stack would like to be laid out, including + * Notification rows, paddings the Shelf and the Footer. + */ + var intrinsicStackHeight: Int = 0 + + /** * When internal NSSL expansion requires the stack to be scrolled (e.g. to keep an expanding * notification in view), that scroll amount can be sent here and it will be handled by the * placeholder @@ -56,11 +62,6 @@ class ScrollViewFields { */ var currentGestureOverscrollConsumer: Consumer<Boolean>? = null /** - * Any time the stack height is recalculated, it should be updated here to be used by the - * placeholder - */ - var stackHeightConsumer: Consumer<Float>? = null - /** * Any time the heads up height is recalculated, it should be updated here to be used by the * placeholder */ @@ -72,8 +73,6 @@ class ScrollViewFields { /** send [isCurrentGestureOverscroll] to the [currentGestureOverscrollConsumer], if present. */ fun sendCurrentGestureOverscroll(isCurrentGestureOverscroll: Boolean) = currentGestureOverscrollConsumer?.accept(isCurrentGestureOverscroll) - /** send the [stackHeight] to the [stackHeightConsumer], if present. */ - fun sendStackHeight(stackHeight: Float) = stackHeightConsumer?.accept(stackHeight) /** send the [headsUpHeight] to the [headsUpHeightConsumer], if present. */ fun sendHeadsUpHeight(headsUpHeight: Float) = headsUpHeightConsumer?.accept(headsUpHeight) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index d0cebae40c5a..0fcfc4b4b2c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -38,6 +38,7 @@ import com.android.systemui.statusbar.notification.footer.ui.view.FooterView; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; +import com.android.systemui.statusbar.notification.shared.NotificationHeadsUpCycling; import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation; import java.util.ArrayList; @@ -75,6 +76,7 @@ public class StackScrollAlgorithm { private float mSmallCornerRadius; private float mLargeCornerRadius; private int mHeadsUpAppearHeightBottom; + private int mHeadsUpCyclingPadding; public StackScrollAlgorithm( Context context, @@ -99,6 +101,8 @@ public class StackScrollAlgorithm { R.dimen.heads_up_status_bar_padding); mHeadsUpAppearStartAboveScreen = res.getDimensionPixelSize( R.dimen.heads_up_appear_y_above_screen); + mHeadsUpCyclingPadding = context.getResources() + .getDimensionPixelSize(R.dimen.heads_up_cycling_padding); mPinnedZTranslationExtra = res.getDimensionPixelSize( R.dimen.heads_up_pinned_elevation); mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height); @@ -348,7 +352,8 @@ public class StackScrollAlgorithm { && !firstHeadsUp && (isHeadsUp || child.isHeadsUpAnimatingAway()) && newNotificationEnd > firstHeadsUpEnd - && !ambientState.isShadeExpanded()) { + && !ambientState.isShadeExpanded() + && !skipClipBottomForCycling(child, ambientState)) { // The bottom of this view is peeking out from under the previous view. // Clip the part that is peeking out. float overlapAmount = newNotificationEnd - firstHeadsUpEnd; @@ -370,6 +375,44 @@ public class StackScrollAlgorithm { } } + /** + * @return Should we skip clipping the bottom clipping when new hun has lower bottom line for + * the hun cycling animation. + */ + private boolean skipClipBottomForCycling(ExpandableView view, AmbientState ambientState) { + if (!NotificationHeadsUpCycling.isEnabled()) return false; + if (!isCyclingOut(view, ambientState)) return false; + // skip bottom clipping if we animate the bottom line + return NotificationHeadsUpCycling.getAnimateTallToShort(); + } + + /** + * Whether the view is the hun that is cycling out by the notification avalanche. + */ + public boolean isCyclingOut(ExpandableView view, AmbientState ambientState) { + if (!NotificationHeadsUpCycling.isEnabled()) return false; + if (!(view instanceof ExpandableNotificationRow)) return false; + return isCyclingOut((ExpandableNotificationRow) view, ambientState); + } + + /** + * Whether the row is the hun that is cycling out by the notification avalanche. + */ + public boolean isCyclingOut(ExpandableNotificationRow row, AmbientState ambientState) { + if (!NotificationHeadsUpCycling.isEnabled()) return false; + String cyclingOutKey = ambientState.getAvalanchePreviousHunKey(); + return row.getEntry().getKey().equals(cyclingOutKey); + } + + /** + * Whether the row is the hun that is cycling in by the notification avalanche. + */ + public boolean isCyclingIn(ExpandableNotificationRow row, AmbientState ambientState) { + if (!NotificationHeadsUpCycling.isEnabled()) return false; + String cyclingInKey = ambientState.getAvalancheShowingHunKey(); + return row.getEntry().getKey().equals(cyclingInKey); + } + /** Updates the dimmed and hiding sensitive states of the children. */ private void updateDimmedAndHideSensitive(AmbientState ambientState, StackScrollAlgorithmState algorithmState) { @@ -799,6 +842,7 @@ public class StackScrollAlgorithm { } ExpandableNotificationRow topHeadsUpEntry = null; + int cyclingInHunHeight = -1; for (int i = 0; i < childCount; i++) { View child = algorithmState.visibleChildren.get(i); if (!(child instanceof ExpandableNotificationRow row)) { @@ -839,6 +883,13 @@ public class StackScrollAlgorithm { childState.setYTranslation( Math.max(childState.getYTranslation(), headsUpTranslation)); childState.height = Math.max(row.getIntrinsicHeight(), childState.height); + if (NotificationHeadsUpCycling.isEnabled()) { + if (isCyclingIn(row, ambientState)) { + if (cyclingInHunHeight == -1) { + cyclingInHunHeight = childState.height; + } + } + } childState.hidden = false; ExpandableViewState topState = topHeadsUpEntry == null ? null : topHeadsUpEntry.getViewState(); @@ -860,6 +911,26 @@ public class StackScrollAlgorithm { } } if (row.isHeadsUpAnimatingAway()) { + if (NotificationHeadsUpCycling.isEnabled() && isCyclingOut(row, ambientState)) { + // If the two HUNs in the cycling animation have different heights, we need + // an extra y translation to align the animation. + int extraTranslation; + if (NotificationHeadsUpCycling.getAnimateTallToShort()) { + if (cyclingInHunHeight > 0) { + extraTranslation = cyclingInHunHeight - childState.height; + } else { + extraTranslation = 0; + } + } else { + extraTranslation = cyclingInHunHeight >= childState.height + ? cyclingInHunHeight - childState.height : 0; + } + extraTranslation += mHeadsUpCyclingPadding; + float inSpaceTranslation = Math.max(childState.getYTranslation(), + headsUpTranslation); + childState.setYTranslation(inSpaceTranslation + extraTranslation); + cyclingInHunHeight = -1; + } else if (NotificationsImprovedHunAnimation.isEnabled() && !ambientState.isDozing()) { if (shouldHunAppearFromBottom(ambientState, childState)) { // move to the bottom of the screen diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index 5963d358443e..5dc544993ddc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.notification.stack; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR; +import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_IN; +import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_CYCLING_OUT; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK; @@ -57,6 +59,7 @@ public class StackStateAnimator { public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150; public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400; public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 400; + public static final int ANIMATION_DURATION_HEADS_UP_CYCLING = 400; public static final int ANIMATION_DURATION_FOLD_TO_AOD = AnimatableClockView.ANIMATION_DURATION_FOLD_TO_AOD; public static final int ANIMATION_DURATION_PRIORITY_CHANGE = 500; @@ -68,6 +71,8 @@ public class StackStateAnimator { @VisibleForTesting int mGoToFullShadeAppearingTranslation; @VisibleForTesting float mHeadsUpAppearStartAboveScreen; + // Padding between the old and new heads up notifications for the hun cycling animation + private float mHeadsUpCyclingPadding; private final ExpandableViewState mTmpState = new ExpandableViewState(); private final AnimationProperties mAnimationProperties; public NotificationStackScrollLayout mHostLayout; @@ -125,6 +130,8 @@ public class StackStateAnimator { R.dimen.go_to_full_shade_appearing_translation); mHeadsUpAppearStartAboveScreen = context.getResources() .getDimensionPixelSize(R.dimen.heads_up_appear_y_above_screen); + mHeadsUpCyclingPadding = context.getResources() + .getDimensionPixelSize(R.dimen.heads_up_cycling_padding); } protected void setLogger(StackStateLogger logger) { @@ -449,7 +456,8 @@ public class StackStateAnimator { } changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR, 0 /* delay */, translationDirection, false /* isHeadsUpAppear */, - startAnimation, postAnimation, getGlobalAnimationFinishedListener()); + startAnimation, postAnimation, getGlobalAnimationFinishedListener(), + ExpandableView.ClipSide.BOTTOM); needsCustomAnimation = true; } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) { @@ -464,6 +472,27 @@ public class StackStateAnimator { .AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) { ExpandableNotificationRow row = (ExpandableNotificationRow) event.mChangingView; row.prepareExpansionChanged(); + } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_CYCLING_IN) { + mHeadsUpAppearChildren.add(changingView); + + mTmpState.copyFrom(changingView.getViewState()); + mTmpState.setYTranslation(changingView.getViewState().getYTranslation() + + getHeadsUpCyclingInYTranslationStart(event.headsUpFromBottom)); + mTmpState.applyToView(changingView); + + // TODO(b/339519404): use a different interpolator + Runnable onAnimationEnd = null; + if (loggable) { + // This only captures HEADS_UP_APPEAR animations, but HUNs can appear with + // normal ADD animations, which would not be logged here. + String finalKey = key; + mLogger.logHUNViewAppearing(key); + onAnimationEnd = () -> { + mLogger.appearAnimationEnded(finalKey); + }; + } + changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_CYCLING, + /* isHeadsUpAppear= */ true, onAnimationEnd); } else if (NotificationsImprovedHunAnimation.isEnabled() && (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR)) { mHeadsUpAppearChildren.add(changingView); @@ -486,6 +515,87 @@ public class StackStateAnimator { } changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR, /* isHeadsUpAppear= */ true, onAnimationEnd); + } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_CYCLING_OUT) { + mHeadsUpDisappearChildren.add(changingView); + Runnable endRunnable = null; + mTmpState.copyFrom(changingView.getViewState()); + + if (changingView.getParent() == null) { + // This notification was actually removed, so we need to add it + // transiently + mHostLayout.addTransientView(changingView, 0); + changingView.setTransientContainer(mHostLayout); + // TODO(b/316404716): remove the hard-coded height + // StackScrollAlgorithm cannot find this view because it has been removed + // from the NSSL. To correctly translate the view to the top or bottom of + // the screen (where it animated from), we need to update its translation. + mTmpState.setYTranslation( + mTmpState.getYTranslation() + 10 + ); + endRunnable = changingView::removeFromTransientContainer; + } + + boolean needsAnimation = true; + if (changingView instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = + (ExpandableNotificationRow) changingView; + if (row.isDismissed()) { + needsAnimation = false; + } + } + if (needsAnimation) { + // We need to add the global animation listener, since once no animations are + // running anymore, the panel will instantly hide itself. We need to wait until + // the animation is fully finished for this though. + final Runnable tmpEndRunnable = endRunnable; + Runnable postAnimation; + Runnable startAnimation; + if (loggable) { + String finalKey1 = key; + final boolean finalIsHeadsUp = isHeadsUp; + final String type = "ANIMATION_TYPE_HEADS_UP_CYCLING_OUT"; + startAnimation = () -> { + mLogger.animationStart(finalKey1, type, finalIsHeadsUp); + changingView.setInRemovalAnimation(true); + }; + postAnimation = () -> { + mLogger.animationEnd(finalKey1, type, finalIsHeadsUp); + changingView.setInRemovalAnimation(false); + if (tmpEndRunnable != null) { + tmpEndRunnable.run(); + } + + }; + } else { + postAnimation = () -> { + changingView.setInRemovalAnimation(false); + if (tmpEndRunnable != null) { + tmpEndRunnable.run(); + } + }; + startAnimation = () -> { + changingView.setInRemovalAnimation(true); + }; + } + long removeAnimationDelay = changingView.performRemoveAnimation( + ANIMATION_DURATION_HEADS_UP_CYCLING, + /* delay= */ 0, + // It's a shame that translationDirection isn't where we do the y + // translation, the actual translation is in StackScrollAlgorithm. + /* translationDirection= */ 0.0f, + /* isHeadsUpAnimation= */ true, + startAnimation, postAnimation, + getGlobalAnimationFinishedListener(), ExpandableView.ClipSide.TOP); + mAnimationProperties.delay += removeAnimationDelay; + mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_CYCLING; + mAnimationProperties.setCustomInterpolator(View.TRANSLATION_Y, + Interpolators.LINEAR); + mAnimationProperties.getAnimationFilter().animateY = true; + mTmpState.animateTo(changingView, mAnimationProperties); + } else if (endRunnable != null) { + endRunnable.run(); + } + needsCustomAnimation |= needsAnimation; } else if (event.animationType == ANIMATION_TYPE_HEADS_UP_APPEAR) { NotificationsImprovedHunAnimation.assertInLegacyMode(); // This item is added, initialize its properties. @@ -565,21 +675,21 @@ public class StackStateAnimator { } }; } else { + startAnimation = () -> { + changingView.setInRemovalAnimation(true); + }; postAnimation = () -> { changingView.setInRemovalAnimation(false); if (tmpEndRunnable != null) { tmpEndRunnable.run(); } }; - startAnimation = () -> { - changingView.setInRemovalAnimation(true); - }; } long removeAnimationDelay = changingView.performRemoveAnimation( ANIMATION_DURATION_HEADS_UP_DISAPPEAR, 0, 0.0f, true /* isHeadsUpAppear */, startAnimation, postAnimation, - getGlobalAnimationFinishedListener()); + getGlobalAnimationFinishedListener(), ExpandableView.ClipSide.BOTTOM); mAnimationProperties.delay += removeAnimationDelay; if (NotificationsImprovedHunAnimation.isEnabled()) { mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR; @@ -607,6 +717,38 @@ public class StackStateAnimator { return -mStackTopMargin - mHeadsUpAppearStartAboveScreen; } + /** + * @param headsUpFromBottom Whether we are showing the HUNs at the bottom of the screen + * @return The start y translation of the HUN cycling in animation + */ + private float getHeadsUpCyclingInYTranslationStart(boolean headsUpFromBottom) { + if (headsUpFromBottom) { + // start from the bottom of the screen + return mHeadsUpAppearHeightBottom + mHeadsUpCyclingPadding; + } + // start from the top of the screen + return -mHeadsUpCyclingPadding; + } + + /** + * @param headsUpFromBottom Whether we are showing the HUNs at the bottom of the screen + * @param oldHunHeight Height of the old HUN + * @param newHunHeight Height of the new HUN + * @return The y translation target value of the HUN cycling out animation + */ + private float getHeadsUpCyclingOutYTranslation( + boolean headsUpFromBottom, + int oldHunHeight, + int newHunHeight + ) { + final float translationDistance = mHeadsUpCyclingPadding + newHunHeight - oldHunHeight; + if (headsUpFromBottom) { + // start from the bottom of the screen + return mHeadsUpAppearHeightBottom - translationDistance; + } + return translationDistance; + } + public void animateOverScrollToAmount(float targetAmount, final boolean onTop, final boolean isRubberbanded) { final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt index 920c9c213060..463c631db32f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt @@ -27,13 +27,6 @@ import kotlinx.coroutines.flow.MutableStateFlow @SysUISingleton class NotificationViewHeightRepository @Inject constructor() { - /** - * The height in px of the contents of notification stack. Depending on the number of - * notifications, this can exceed the space available on screen to show notifications, at which - * point the notification stack should become scrollable. - */ - val stackHeight = MutableStateFlow(0f) - /** The height in px of the current heads up notification. */ val headsUpHeight = MutableStateFlow(0f) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt index b94da388cef4..365ead699c65 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt @@ -65,13 +65,6 @@ constructor( } .distinctUntilChanged() - /** - * The height in px of the contents of notification stack. Depending on the number of - * notifications, this can exceed the space available on screen to show notifications, at which - * point the notification stack should become scrollable. - */ - val stackHeight: StateFlow<Float> = viewHeightRepository.stackHeight.asStateFlow() - /** The height in px of the contents of the HUN. */ val headsUpHeight: StateFlow<Float> = viewHeightRepository.headsUpHeight.asStateFlow() @@ -123,11 +116,6 @@ constructor( placeholderRepository.shadeScrimBounds.value = bounds } - /** Sets the height of the contents of the notification stack. */ - fun setStackHeight(height: Float) { - viewHeightRepository.stackHeight.value = height - } - /** Sets the height of heads up notification. */ fun setHeadsUpHeight(height: Float) { viewHeightRepository.headsUpHeight.value = height diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt index 2c8884504c32..14b882f974d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt @@ -25,6 +25,13 @@ import java.util.function.Consumer * notification stack, but is otherwise agnostic to the content. */ interface NotificationScrollView { + + /** + * Height in view pixels at which the Notification Stack would like to be laid out, including + * Notification rows, paddings the Shelf and the Footer. + */ + val intrinsicStackHeight: Int + /** * Since this is an interface rather than a literal View, this provides cast-like access to the * underlying view. @@ -53,8 +60,6 @@ interface NotificationScrollView { fun setSyntheticScrollConsumer(consumer: Consumer<Float>?) /** Set a consumer for current gesture overscroll events */ fun setCurrentGestureOverscrollConsumer(consumer: Consumer<Boolean>?) - /** Set a consumer for stack height changed events */ - fun setStackHeightConsumer(consumer: Consumer<Float>?) /** Set a consumer for heads up height changed events */ fun setHeadsUpHeightConsumer(consumer: Consumer<Float>?) @@ -66,4 +71,10 @@ interface NotificationScrollView { /** Sets whether the view is displayed in doze mode. */ fun setDozing(dozing: Boolean) + + /** Sets a listener to be notified, when the stack height might have changed. */ + fun addStackHeightChangedListener(runnable: Runnable) + + /** @see addStackHeightChangedListener */ + fun removeStackHeightChangedListener(runnable: Runnable) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt index 26f7ad775f1d..3c44713a1df5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt @@ -90,12 +90,10 @@ constructor( launchAndDispose { view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer) view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer) - view.setStackHeightConsumer(viewModel.stackHeightConsumer) view.setHeadsUpHeightConsumer(viewModel.headsUpHeightConsumer) DisposableHandle { view.setSyntheticScrollConsumer(null) view.setCurrentGestureOverscrollConsumer(null) - view.setStackHeightConsumer(null) view.setHeadsUpHeightConsumer(null) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt index b2184db0879d..082f6b6f11aa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt @@ -151,8 +151,6 @@ constructor( */ val currentGestureOverscrollConsumer: (Boolean) -> Unit = stackAppearanceInteractor::setCurrentGestureOverscroll - /** Receives the height of the contents of the notification stack. */ - val stackHeightConsumer: (Float) -> Unit = stackAppearanceInteractor::setStackHeight /** Receives the height of the heads up notification. */ val headsUpHeightConsumer: (Float) -> Unit = stackAppearanceInteractor::setHeadsUpHeight diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt index 11eaf54efe47..736058ac3a78 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt @@ -87,13 +87,6 @@ constructor( val shadeScrimRounding: Flow<ShadeScrimRounding> = interactor.shadeScrimRounding.dumpWhileCollecting("shadeScrimRounding") - /** - * The height in px of the contents of notification stack. Depending on the number of - * notifications, this can exceed the space available on screen to show notifications, at which - * point the notification stack should become scrollable. - */ - val stackHeight: StateFlow<Float> = interactor.stackHeight.dumpValue("stackHeight") - /** The height in px of the contents of the HUN. */ val headsUpHeight: StateFlow<Float> = interactor.headsUpHeight.dumpValue("headsUpHeight") diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java index e93c0f6a5559..7dac77e91730 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java @@ -59,6 +59,7 @@ import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeHeaderController; import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -82,6 +83,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba private final com.android.systemui.shade.ShadeController mShadeController; private final CommandQueue mCommandQueue; private final PanelExpansionInteractor mPanelExpansionInteractor; + private final Lazy<ShadeInteractor> mShadeInteractorLazy; private final ShadeHeaderController mShadeHeaderController; private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; private final MetricsLogger mMetricsLogger; @@ -121,6 +123,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba ShadeController shadeController, CommandQueue commandQueue, PanelExpansionInteractor panelExpansionInteractor, + Lazy<ShadeInteractor> shadeInteractorLazy, ShadeHeaderController shadeHeaderController, RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, MetricsLogger metricsLogger, @@ -148,6 +151,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba mShadeController = shadeController; mCommandQueue = commandQueue; mPanelExpansionInteractor = panelExpansionInteractor; + mShadeInteractorLazy = shadeInteractorLazy; mShadeHeaderController = shadeHeaderController; mRemoteInputQuickSettingsDisabler = remoteInputQuickSettingsDisabler; mMetricsLogger = metricsLogger; @@ -487,14 +491,23 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba } @Override - public void togglePanel() { - if (mPanelExpansionInteractor.isPanelExpanded()) { + public void toggleNotificationsPanel() { + if (mShadeInteractorLazy.get().isAnyExpanded().getValue()) { mShadeController.animateCollapseShade(); } else { mShadeController.animateExpandShade(); } } + @Override + public void toggleQuickSettingsPanel() { + if (mShadeInteractorLazy.get().isQsExpanded().getValue()) { + mShadeController.animateCollapseShade(); + } else { + mShadeController.animateExpandQs(); + } + } + private boolean isGoingToSleep() { return mWakefulnessLifecycle.getWakefulness() == WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP; 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 be6bef74565a..aa55f375b2eb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone; +import static android.app.StatusBarManager.DISABLE_HOME; import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.WindowVisibleState; @@ -1006,14 +1007,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // this handling this post-init task. We force an update in this case, and use a new // token to not conflict with any other disabled flags already requested by SysUI Binder token = new Binder(); - int userId = mContext.getUserId(); - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setNavigationHomeDisabled(true); - mBarService.disableForUser(info, token, mContext.getPackageName(), - userId, "set the initial view visibility"); - - mBarService.disableForUser(new StatusBarManager.DisableInfo(), token, - mContext.getPackageName(), userId, "set the initial view visibility"); + mBarService.disable(DISABLE_HOME, token, mContext.getPackageName()); + mBarService.disable(0, token, mContext.getPackageName()); } catch (RemoteException ex) { ex.rethrowFromSystemServer(); } @@ -2184,7 +2179,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } if (mStatusBarStateController.leaveOpenOnKeyguardHide()) { if (!mStatusBarStateController.isKeyguardRequested()) { - mStatusBarStateController.setLeaveOpenOnKeyguardHide(false); + if (!MigrateClocksToBlueprint.isEnabled()) { + mStatusBarStateController.setLeaveOpenOnKeyguardHide(false); + } } long delay = mKeyguardStateController.calculateGoingToFullShadeDelay(); mLockscreenShadeTransitionController.onHideKeyguard(delay, previousState); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index f0dab3ba1829..b71564627223 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -696,6 +696,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb Trace.beginSection("StatusBarKeyguardViewManager#show"); mNotificationShadeWindowController.setKeyguardShowing(true); if (SceneContainerFlag.isEnabled()) { + // TODO(b/336581871): add sceneState? mSceneInteractorLazy.get().changeScene( Scenes.Lockscreen, "StatusBarKeyguardViewManager.show"); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt index d9d909a49781..fc54f140dec5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt @@ -33,6 +33,18 @@ data class SubscriptionModel( */ val isOpportunistic: Boolean = false, + /** + * True if this subscription **only** supports non-terrestrial networks (NTN) and false + * otherwise. (non-terrestrial == satellite) + * + * Note that we intend to filter these subscriptions out, because these connections are actually + * supported by + * [com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository]. See + * [com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor] for + * the filtering. + */ + val isExclusivelyNonTerrestrial: Boolean = false, + /** Subscriptions in the same group may be filtered or treated as a single subscription */ val groupUuid: ParcelUuid? = null, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt index 22785979f3ae..425c58b0074b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt @@ -23,6 +23,7 @@ import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import kotlinx.coroutines.flow.StateFlow @@ -76,7 +77,17 @@ interface MobileConnectionRepository { */ val isInService: StateFlow<Boolean> - /** Reflects [android.telephony.ServiceState.isUsingNonTerrestrialNetwork] */ + /** + * True if this subscription is actively connected to a non-terrestrial network and false + * otherwise. Reflects [android.telephony.ServiceState.isUsingNonTerrestrialNetwork]. + * + * Notably: This value reflects that this subscription is **currently** using a non-terrestrial + * network, because some subscriptions can switch between terrestrial and non-terrestrial + * networks. [SubscriptionModel.isExclusivelyNonTerrestrial] reflects whether a subscription is + * configured to exclusively connect to non-terrestrial networks. [isNonTerrestrial] can change + * during the lifetime of a subscription but [SubscriptionModel.isExclusivelyNonTerrestrial] + * will stay constant. + */ val isNonTerrestrial: StateFlow<Boolean> /** True if [android.telephony.SignalStrength] told us that this connection is using GSM */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index 5d91ef323ead..0073e9cd3dd8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -424,6 +424,7 @@ constructor( SubscriptionModel( subscriptionId = subscriptionId, isOpportunistic = isOpportunistic, + isExclusivelyNonTerrestrial = isOnlyNonTerrestrialNetwork, groupUuid = groupUuid, carrierName = carrierName.toString(), profileClass = profileClass, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt index d555c47f4da2..91d7ca65b30d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt @@ -172,21 +172,33 @@ constructor( private val unfilteredSubscriptions: Flow<List<SubscriptionModel>> = mobileConnectionsRepo.subscriptions - /** - * Any filtering that we can do based purely on the info of each subscription. Currently this - * only applies the ProfileClass-based filter, but if we need other they can go here - */ + /** Any filtering that we can do based purely on the info of each subscription individually. */ private val subscriptionsBasedFilteredSubs = - unfilteredSubscriptions.map { subs -> applyProvisioningFilter(subs) }.distinctUntilChanged() + unfilteredSubscriptions + .map { it.filterBasedOnProvisioning().filterBasedOnNtn() } + .distinctUntilChanged() - private fun applyProvisioningFilter(subs: List<SubscriptionModel>): List<SubscriptionModel> = + private fun List<SubscriptionModel>.filterBasedOnProvisioning(): List<SubscriptionModel> = if (!featureFlagsClassic.isEnabled(FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS)) { - subs + this } else { - subs.filter { it.profileClass != PROFILE_CLASS_PROVISIONING } + this.filter { it.profileClass != PROFILE_CLASS_PROVISIONING } } /** + * Subscriptions that exclusively support non-terrestrial networks should **never** directly + * show any iconography in the status bar. These subscriptions only exist to provide a backing + * for the device-based satellite connections, and the iconography for those connections are + * already being handled in + * [com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository]. We + * need to filter out those subscriptions here so we guarantee the subscription never turns into + * an icon. See b/336881301. + */ + private fun List<SubscriptionModel>.filterBasedOnNtn(): List<SubscriptionModel> { + return this.filter { !it.isExclusivelyNonTerrestrial } + } + + /** * Generally, SystemUI wants to show iconography for each subscription that is listed by * [SubscriptionManager]. However, in the case of opportunistic subscriptions, we want to only * show a single representation of the pair of subscriptions. The docs define opportunistic as: @@ -204,12 +216,8 @@ constructor( subscriptionsBasedFilteredSubs, mobileConnectionsRepo.activeMobileDataSubscriptionId, connectivityRepository.vcnSubId, - ) { unfilteredSubs, activeId, vcnSubId -> - filterSubsBasedOnOpportunistic( - unfilteredSubs, - activeId, - vcnSubId, - ) + ) { preFilteredSubs, activeId, vcnSubId -> + filterSubsBasedOnOpportunistic(preFilteredSubs, activeId, vcnSubId) } .distinctUntilChanged() .logDiffsForTable( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt index 2670a95329cc..fa8a7d8ae587 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt @@ -253,6 +253,7 @@ class AvalancheController @Inject constructor( if (nextList.isEmpty()) { log { "NO MORE TO SHOW" } + previousHunKey = "" return } diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/LocationControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/LocationControllerExt.kt new file mode 100644 index 000000000000..ee1b5655f7be --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/LocationControllerExt.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.kotlin + +import com.android.systemui.statusbar.policy.LocationController +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.onStart + +fun LocationController.isLocationEnabledFlow(): Flow<Boolean> { + return conflatedCallbackFlow { + val locationCallback = + object : LocationController.LocationChangeCallback { + override fun onLocationSettingsChanged(locationEnabled: Boolean) { + trySend(locationEnabled) + } + } + addCallback(locationCallback) + awaitClose { removeCallback(locationCallback) } + } + .onStart { emit(isLocationEnabled) } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt index ab6a37bccc11..d9e19d83cbf4 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/SharedPreferencesExt.kt @@ -17,23 +17,15 @@ package com.android.systemui.util.kotlin import android.content.SharedPreferences -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.mapNotNull object SharedPreferencesExt { - /** - * Returns a flow of [Unit] that is invoked each time shared preference is updated. - * - * @param key Optional key to limit updates to a particular key. - */ - fun SharedPreferences.observe(key: String? = null): Flow<Unit> = - conflatedCallbackFlow { - val listener = - SharedPreferences.OnSharedPreferenceChangeListener { _, key -> trySend(key) } - registerOnSharedPreferenceChangeListener(listener) - awaitClose { unregisterOnSharedPreferenceChangeListener(listener) } - } - .mapNotNull { changedKey -> if ((key ?: changedKey) == changedKey) Unit else null } + /** Returns a flow of [Unit] that is invoked each time shared preference is updated. */ + fun SharedPreferences.observe(): Flow<Unit> = conflatedCallbackFlow { + val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> trySend(Unit) } + registerOnSharedPreferenceChangeListener(listener) + awaitClose { unregisterOnSharedPreferenceChangeListener(listener) } + } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index c69fb662b67d..e56893a36a57 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -174,9 +174,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private static final String TYPE_DISMISS = "dismiss"; /** Volume dialog slider animation. */ private static final String TYPE_UPDATE = "update"; - static final int PROGRESS_HAPTICS_DISABLED = 0; - static final int PROGRESS_HAPTICS_EAGER = 1; - static final int PROGRESS_HAPTICS_ANIMATED = 2; /** * TODO(b/290612381): remove lingering animations or tolerate them @@ -285,7 +282,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, @GuardedBy("mSafetyWarningLock") private CsdWarningDialog mCsdDialog; private boolean mHovering = false; - private final boolean mShowActiveStreamOnly; + private final boolean mIsTv; private boolean mConfigChanged = false; private boolean mIsAnimatingDismiss = false; private boolean mHasSeenODICaptionsTooltip; @@ -346,7 +343,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mConfigurationController = configurationController; mMediaOutputDialogManager = mediaOutputDialogManager; mCsdWarningDialogFactory = csdWarningDialogFactory; - mShowActiveStreamOnly = showActiveStreamOnly(); + mIsTv = isTv(); mHasSeenODICaptionsTooltip = Prefs.getBoolean(context, Prefs.Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, false); mShowLowMediaVolumeIcon = @@ -1635,7 +1632,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, Trace.endSection(); } - private boolean showActiveStreamOnly() { + private boolean isTv() { return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK) || mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION); } @@ -1647,7 +1644,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, return true; } - if (!mShowActiveStreamOnly) { + if (!mIsTv) { if (row.stream == AudioSystem.STREAM_ACCESSIBILITY) { return mShowA11yStream; } @@ -2092,6 +2089,11 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } final int newProgress = getProgressFromVolume(row.ss, row.slider, vlevel); if (progress != newProgress) { + if (mIsTv) { + // don't animate slider on TVs + row.slider.setProgress(newProgress, false); + return; + } if (mShowing && rowVisible) { // animate! if (row.anim != null && row.anim.isRunning() @@ -2112,7 +2114,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, row.anim.setIntValues(progress, newProgress); // The animator can't keep up with the volume changes so haptics need to be // triggered here. This happens when the volume keys are continuously pressed. - row.deliverOnProgressChangedHaptics(false, newProgress, PROGRESS_HAPTICS_EAGER); + row.deliverOnProgressChangedHaptics(false, newProgress); } row.animTargetProgress = newProgress; row.anim.setDuration(UPDATE_ANIMATION_DURATION); @@ -2127,13 +2129,14 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } } - @VisibleForTesting int progressHapticsForStream(int stream) { + @VisibleForTesting + boolean canDeliverProgressHapticsToStream(int stream, boolean fromUser, int progress) { for (VolumeRow row: mRows) { if (row.stream == stream) { - return row.mProgressHapticsType; + return row.deliverOnProgressChangedHaptics(fromUser, progress); } } - return PROGRESS_HAPTICS_DISABLED; + return false; } private void recheckH(VolumeRow row) { @@ -2527,8 +2530,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, if (fromUser || mRow.animTargetProgress == progress) { // Deliver user-generated slider haptics immediately, or when the animation // completes - mRow.deliverOnProgressChangedHaptics( - fromUser, progress, PROGRESS_HAPTICS_ANIMATED); + mRow.deliverOnProgressChangedHaptics(fromUser, progress); } } if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream) @@ -2641,7 +2643,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private int animTargetProgress; private int lastAudibleLevel = 1; private SeekbarHapticPlugin mHapticPlugin; - private int mProgressHapticsType = PROGRESS_HAPTICS_DISABLED; void setIcon(int iconRes, Resources.Theme theme) { if (icon != null) { @@ -2683,15 +2684,23 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, slider.setOnTouchListener(null); } - void deliverOnProgressChangedHaptics(boolean fromUser, int progress, int hapticsType) { - if (mHapticPlugin == null) return; + /** + * Deliver haptics when the progress of the slider has changed. + * + * @param fromUser True if the progress changed was caused by the user. + * @param progress The progress value of the slider. + * @return True if haptics were successfully delivered. False otherwise. This will happen + * if mHapticPlugin is null + */ + boolean deliverOnProgressChangedHaptics(boolean fromUser, int progress) { + if (mHapticPlugin == null) return false; mHapticPlugin.onProgressChanged(slider, progress, fromUser); if (!fromUser) { // Consider a change from program as the volume key being continuously pressed mHapticPlugin.onKeyDown(); } - mProgressHapticsType = hapticsType; + return true; } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt index 038633810fd7..c08cd64e4e8c 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt @@ -49,6 +49,11 @@ constructor( private val uiEventLogger: UiEventLogger, ) : SliderViewModel { + private val streamsAffectedByRing = + setOf( + AudioManager.STREAM_RING, + AudioManager.STREAM_NOTIFICATION, + ) private val audioStream = audioStreamWrapper.audioStream private val iconsByStream = mapOf( @@ -125,15 +130,42 @@ constructor( isEnabled: Boolean, ringerMode: RingerMode, ): State { + val label = + labelsByStream[audioStream]?.let(context::getString) + ?: error("No label for the stream: $audioStream") return State( value = volume.toFloat(), valueRange = volumeRange.first.toFloat()..volumeRange.last.toFloat(), icon = getIcon(ringerMode), - label = labelsByStream[audioStream]?.let(context::getString) - ?: error("No label for the stream: $audioStream"), + label = label, disabledMessage = disabledTextByStream[audioStream]?.let(context::getString), isEnabled = isEnabled, a11yStep = volumeRange.step, + a11yClickDescription = + context.getString( + if (isMuted) { + R.string.volume_panel_hint_unmute + } else { + R.string.volume_panel_hint_mute + }, + label, + ), + a11yStateDescription = + if (volume == volumeRange.first) { + context.getString( + if (audioStream.value in streamsAffectedByRing) { + if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { + R.string.volume_panel_hint_vibrate + } else { + R.string.volume_panel_hint_muted + } + } else { + R.string.volume_panel_hint_muted + } + ) + } else { + null + }, audioStreamModel = this, isMutable = audioVolumeInteractor.isAffectedByMute(audioStream), ) @@ -143,27 +175,14 @@ constructor( val isMutedOrNoVolume = isMuted || volume == minVolume val iconRes = if (isMutedOrNoVolume) { - when (audioStream.value) { - AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_off - AudioManager.STREAM_BLUETOOTH_SCO -> R.drawable.ic_volume_off - AudioManager.STREAM_VOICE_CALL -> R.drawable.ic_volume_off - AudioManager.STREAM_RING -> - if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { - R.drawable.ic_volume_ringer_vibrate - } else { - R.drawable.ic_volume_off - } - AudioManager.STREAM_NOTIFICATION -> - if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { - R.drawable.ic_volume_ringer_vibrate - } else { - R.drawable.ic_volume_off - } - AudioManager.STREAM_ALARM -> R.drawable.ic_volume_off - else -> { - Log.wtf(TAG, "No icon for the stream: $audioStream") + if (audioStream.value in streamsAffectedByRing) { + if (ringerMode.value == AudioManager.RINGER_MODE_VIBRATE) { + R.drawable.ic_volume_ringer_vibrate + } else { R.drawable.ic_volume_off } + } else { + R.drawable.ic_volume_off } } else { iconsByStream[audioStream] @@ -186,6 +205,8 @@ constructor( override val disabledMessage: String?, override val isEnabled: Boolean, override val a11yStep: Int, + override val a11yClickDescription: String?, + override val a11yStateDescription: String?, override val isMutable: Boolean, val audioStreamModel: AudioStreamModel, ) : SliderState diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt index 956ab66ac0c3..10714d1f41af 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt @@ -68,7 +68,7 @@ constructor( icon = Icon.Resource(R.drawable.ic_cast, null), label = context.getString(R.string.media_device_cast), isEnabled = true, - a11yStep = 1 + a11yStep = 1, ) } @@ -85,6 +85,12 @@ constructor( override val isMutable: Boolean get() = false + + override val a11yClickDescription: String? + get() = null + + override val a11yStateDescription: String? + get() = null } @AssistedFactory diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt index d71a9d8dae94..c951928bb977 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderState.kt @@ -34,6 +34,8 @@ sealed interface SliderState { * enough to trigger rounding to the correct value. */ val a11yStep: Int + val a11yClickDescription: String? + val a11yStateDescription: String? val disabledMessage: String? val isMutable: Boolean @@ -44,6 +46,8 @@ sealed interface SliderState { override val label: String = "" override val disabledMessage: String? = null override val a11yStep: Int = 0 + override val a11yClickDescription: String? = null + override val a11yStateDescription: String? = null override val isEnabled: Boolean = true override val isMutable: Boolean = false } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt index 26d6a9a0153d..4b4d69a31db4 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt @@ -31,13 +31,15 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.launch @@ -53,12 +55,39 @@ class AudioVolumeComponentViewModel constructor( @VolumePanelScope private val scope: CoroutineScope, mediaOutputInteractor: MediaOutputInteractor, - private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor, + mediaDeviceSessionInteractor: MediaDeviceSessionInteractor, private val streamSliderViewModelFactory: AudioStreamSliderViewModel.Factory, private val castVolumeSliderViewModelFactory: CastVolumeSliderViewModel.Factory, streamsInteractor: AudioSlidersInteractor, ) { + private val mutableIsExpanded = MutableStateFlow<Boolean?>(null) + private val isPlaybackActive: Flow<Boolean?> = + mediaOutputInteractor.defaultActiveMediaSession + .filterData() + .flatMapLatest { session -> + if (session == null) { + flowOf(false) + } else { + mediaDeviceSessionInteractor.playbackState(session).map { it?.isActive == true } + } + } + .onEach { isPlaybackActive -> mutableIsExpanded.value = !isPlaybackActive } + .stateIn(scope, SharingStarted.Eagerly, null) + private val portraitExpandable: Flow<SlidersExpandableViewModel> = + isPlaybackActive + .filterNotNull() + .flatMapLatest { isActive -> + if (isActive) { + mutableIsExpanded.filterNotNull().map { isExpanded -> + SlidersExpandableViewModel.Expandable(isExpanded) + } + } else { + flowOf(SlidersExpandableViewModel.Fixed) + } + } + .stateIn(scope, SharingStarted.Eagerly, SlidersExpandableViewModel.Unavailable) + val sliderViewModels: StateFlow<List<SliderViewModel>> = streamsInteractor.volumePanelSliders .transformLatest { sliderTypes -> @@ -76,24 +105,16 @@ constructor( } .stateIn(scope, SharingStarted.Eagerly, emptyList()) - private val mutableIsExpanded = MutableSharedFlow<Boolean>() - - val isExpanded: StateFlow<Boolean> = - merge( - mutableIsExpanded, - mediaOutputInteractor.defaultActiveMediaSession.filterData().flatMapLatest { session - -> - if (session == null) flowOf(true) - else - mediaDeviceSessionInteractor.playbackState(session).map { - it?.isActive != true - } - }, - ) - .stateIn(scope, SharingStarted.Eagerly, false) + fun isExpandable(isPortrait: Boolean): Flow<SlidersExpandableViewModel> { + return if (isPortrait) { + portraitExpandable + } else { + flowOf(SlidersExpandableViewModel.Fixed) + } + } fun onExpandedChanged(isExpanded: Boolean) { - scope.launch { mutableIsExpanded.emit(isExpanded) } + scope.launch { mutableIsExpanded.value = isExpanded } } private fun CoroutineScope.createSessionViewModel( diff --git a/keystore/java/android/security/KeyStore.java b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/SlidersExpandableViewModel.kt index d1d7c145680f..19b9ead88ebb 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/SlidersExpandableViewModel.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 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. @@ -14,18 +14,18 @@ * limitations under the License. */ -package android.security; +package com.android.systemui.volume.panel.component.volume.ui.viewmodel /** - * This class provides some constants and helper methods related to Android's Keystore service. - * This class was originally much larger, but its functionality was superseded by other classes. - * It now just contains a few remaining pieces for which the users haven't been updated yet. - * You may be looking for {@link java.security.KeyStore} instead. - * - * @hide + * Models expandability state of the + * [com.android.systemui.volume.panel.component.volume.ui.composable.VolumeSlidersComponent]. */ -public class KeyStore { +sealed interface SlidersExpandableViewModel { + + /** [SlidersExpandableViewModel] is not loaded. */ + data object Unavailable : SlidersExpandableViewModel + + data class Expandable(val isExpanded: Boolean) : SlidersExpandableViewModel - // Used for UID field to indicate the calling UID. - public static final int UID_SELF = -1; + data object Fixed : SlidersExpandableViewModel } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index b86a7c92ba47..e073f7cde826 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -98,7 +98,7 @@ public final class WMShell implements CoreStartable, CommandQueue.Callbacks { private static final String TAG = WMShell.class.getName(); - private static final int INVALID_SYSUI_STATE_MASK = + private static final long INVALID_SYSUI_STATE_MASK = SYSUI_STATE_DIALOG_SHOWING | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index e72027a921b7..6f550ba70045 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -371,7 +371,7 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test - fun listenForTransitionToLSFromOccluded_updatesClockDozeAmountToOne() = + fun listenForTransitionToLSFromOccluded_updatesClockDozeAmountToZero() = runBlocking(IMMEDIATE) { val transitionStep = MutableStateFlow(TransitionStep()) whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.LOCKSCREEN)) @@ -434,6 +434,27 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test + fun listenForAnyStateToDozingTransition_UpdatesClockDozeAmountToOne() = + runBlocking(IMMEDIATE) { + val transitionStep = MutableStateFlow(TransitionStep()) + whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.DOZING)) + .thenReturn(transitionStep) + + val job = underTest.listenForAnyStateToDozingTransition(this) + transitionStep.value = + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DOZING, + transitionState = TransitionState.STARTED, + ) + yield() + + verify(animations, times(2)).doze(1f) + + job.cancel() + } + + @Test fun unregisterListeners_validate() = runBlocking(IMMEDIATE) { underTest.unregisterListeners() diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java index 25e5470e2781..3164f8e11593 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java @@ -16,6 +16,8 @@ package com.android.systemui.accessibility; +import static com.android.systemui.accessibility.Magnification.DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -23,11 +25,17 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.display.DisplayManager; import android.os.RemoteException; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -39,6 +47,7 @@ import android.view.accessibility.IRemoteMagnificationAnimationCallback; import androidx.test.filters.SmallTest; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.model.SysUiState; import com.android.systemui.recents.OverviewProxyService; @@ -47,6 +56,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.util.settings.SecureSettings; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -58,9 +68,12 @@ import org.mockito.MockitoAnnotations; */ @SmallTest @RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper +@TestableLooper.RunWithLooper(setAsMainLooper = true) public class IMagnificationConnectionTest extends SysuiTestCase { + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY; @Mock private AccessibilityManager mAccessibilityManager; @@ -90,6 +103,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { private IMagnificationConnection mIMagnificationConnection; private Magnification mMagnification; private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext); + private TestableLooper mTestableLooper; @Before public void setUp() throws Exception { @@ -100,8 +114,10 @@ public class IMagnificationConnectionTest extends SysuiTestCase { return null; }).when(mAccessibilityManager).setMagnificationConnection( any(IMagnificationConnection.class)); + mTestableLooper = TestableLooper.get(this); + assertNotNull(mTestableLooper); mMagnification = new Magnification(getContext(), - getContext().getMainThreadHandler(), getContext().getMainExecutor(), mCommandQueue, + mTestableLooper.getLooper(), getContext().getMainExecutor(), mCommandQueue, mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker, getContext().getSystemService(DisplayManager.class), mA11yLogger); mMagnification.mWindowMagnificationControllerSupplier = @@ -122,7 +138,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { public void enableWindowMagnification_passThrough() throws RemoteException { mIMagnificationConnection.enableWindowMagnification(TEST_DISPLAY, 3.0f, Float.NaN, Float.NaN, 0f, 0f, mAnimationCallback); - waitForIdleSync(); + processAllPendingMessages(); verify(mWindowMagnificationController).enableWindowMagnification(eq(3.0f), eq(Float.NaN), eq(Float.NaN), eq(0f), eq(0f), eq(mAnimationCallback)); @@ -131,7 +147,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { @Test public void onFullscreenMagnificationActivationChanged_passThrough() throws RemoteException { mIMagnificationConnection.onFullscreenMagnificationActivationChanged(TEST_DISPLAY, true); - waitForIdleSync(); + processAllPendingMessages(); verify(mFullscreenMagnificationController) .onFullscreenMagnificationActivationChanged(eq(true)); @@ -141,7 +157,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { public void disableWindowMagnification_deleteWindowMagnification() throws RemoteException { mIMagnificationConnection.disableWindowMagnification(TEST_DISPLAY, mAnimationCallback); - waitForIdleSync(); + processAllPendingMessages(); verify(mWindowMagnificationController).deleteWindowMagnification( mAnimationCallback); @@ -150,7 +166,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { @Test public void setScaleForWindowMagnification() throws RemoteException { mIMagnificationConnection.setScaleForWindowMagnification(TEST_DISPLAY, 3.0f); - waitForIdleSync(); + processAllPendingMessages(); verify(mWindowMagnificationController).setScale(3.0f); } @@ -158,7 +174,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { @Test public void moveWindowMagnifier() throws RemoteException { mIMagnificationConnection.moveWindowMagnifier(TEST_DISPLAY, 100f, 200f); - waitForIdleSync(); + processAllPendingMessages(); verify(mWindowMagnificationController).moveWindowMagnifier(100f, 200f); } @@ -167,37 +183,102 @@ public class IMagnificationConnectionTest extends SysuiTestCase { public void moveWindowMagnifierToPosition() throws RemoteException { mIMagnificationConnection.moveWindowMagnifierToPosition(TEST_DISPLAY, 100f, 200f, mAnimationCallback); - waitForIdleSync(); + processAllPendingMessages(); verify(mWindowMagnificationController).moveWindowMagnifierToPosition( eq(100f), eq(200f), any(IRemoteMagnificationAnimationCallback.class)); } @Test - public void showMagnificationButton() throws RemoteException { + @RequiresFlagsDisabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON) + public void showMagnificationButton_flagOff_directlyShowButton() throws RemoteException { // magnification settings panel should not be showing assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY)); mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); - waitForIdleSync(); + processAllPendingMessages(); + + verify(mModeSwitchesController).showButton(TEST_DISPLAY, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON) + public void showMagnificationButton_flagOn_delayedShowButton() throws RemoteException { + // magnification settings panel should not be showing + assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY)); + mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + // This processAllPendingMessages lets the IMagnificationConnection to delegate the + // showMagnificationButton request to Magnification. + processAllPendingMessages(); + + // The delayed message would be processed after DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS. + // So call this processAllPendingMessages with a timeout to verify the showButton + // will be called. + int timeout = DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS + 100; + processAllPendingMessages(timeout); verify(mModeSwitchesController).showButton(TEST_DISPLAY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); } @Test + public void showMagnificationButton_settingsPanelShowing_doNotShowButton() + throws RemoteException { + when(mMagnificationSettingsController.isMagnificationSettingsShowing()).thenReturn(true); + + mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + // This processAllPendingMessages lets the IMagnificationConnection to delegate the + // showMagnificationButton request to Magnification. + processAllPendingMessages(); + + // If the flag is on, the isMagnificationSettingsShowing will be checked after timeout, so + // process all message after a timeout here to verify the showButton will not be called. + int timeout = Flags.delayShowMagnificationButton() + ? DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS + 100 + : 0; + processAllPendingMessages(timeout); + verify(mModeSwitchesController, never()).showButton(TEST_DISPLAY, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + } + + @Test public void removeMagnificationButton() throws RemoteException { mIMagnificationConnection.removeMagnificationButton(TEST_DISPLAY); - waitForIdleSync(); + processAllPendingMessages(); verify(mModeSwitchesController).removeButton(TEST_DISPLAY); } @Test + @RequiresFlagsEnabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON) + public void removeMagnificationButton_delayingShowButton_doNotShowButtonAfterTimeout() + throws RemoteException { + // magnification settings panel should not be showing + assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY)); + + mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + mIMagnificationConnection.removeMagnificationButton(TEST_DISPLAY); + // This processAllPendingMessages lets the IMagnificationConnection to delegate the + // requests to Magnification. + processAllPendingMessages(); + + // Call this processAllPendingMessages with a timeout to ensure the delayed show button + // message should be removed and thus the showButton will not be called after timeout. + int timeout = DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS + 100; + processAllPendingMessages(/* timeForwardMs= */ timeout); + verify(mModeSwitchesController, never()).showButton(TEST_DISPLAY, + Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN); + } + + @Test public void removeMagnificationSettingsPanel() throws RemoteException { mIMagnificationConnection.removeMagnificationSettingsPanel(TEST_DISPLAY); - waitForIdleSync(); + processAllPendingMessages(); verify(mMagnificationSettingsController).closeMagnificationSettings(); } @@ -208,7 +289,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase { final float testScale = 3.0f; mIMagnificationConnection.onUserMagnificationScaleChanged( testUserId, TEST_DISPLAY, testScale); - waitForIdleSync(); + processAllPendingMessages(); assertTrue(mMagnification.mUsersScales.contains(testUserId)); assertEquals(mMagnification.mUsersScales.get(testUserId).get(TEST_DISPLAY), @@ -216,6 +297,17 @@ public class IMagnificationConnectionTest extends SysuiTestCase { verify(mMagnificationSettingsController).setMagnificationScale(eq(testScale)); } + private void processAllPendingMessages() { + processAllPendingMessages(/* timeForwardMs=*/ 0); + } + + private void processAllPendingMessages(int timeForwardMs) { + if (timeForwardMs > 0) { + mTestableLooper.moveTimeForward(timeForwardMs); + } + mTestableLooper.processAllMessages(); + } + private class FakeWindowMagnificationControllerSupplier extends DisplayIdIndexSupplier<WindowMagnificationController> { @@ -229,7 +321,6 @@ public class IMagnificationConnectionTest extends SysuiTestCase { } } - private class FakeFullscreenMagnificationControllerSupplier extends DisplayIdIndexSupplier<FullscreenMagnificationController> { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java index 6dc5b7212cd0..bbdd8050a142 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java @@ -27,6 +27,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_M 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.ArgumentMatchers.isNull; import static org.mockito.Mockito.doAnswer; @@ -104,7 +105,7 @@ public class MagnificationTest extends SysuiTestCase { }).when(mAccessibilityManager).setMagnificationConnection( any(IMagnificationConnection.class)); - when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState); + when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState); doAnswer(invocation -> { mMagnification.mMagnificationSettingsControllerCallback diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt index bf6ca0684398..e371b39faab4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt @@ -46,12 +46,12 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyBoolean -import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyLong import org.mockito.Mock import org.mockito.Mockito.spy import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever private const val ON: Int = 1 private const val OFF: Int = 0 @@ -90,7 +90,7 @@ class FontScalingDialogDelegateTest : SysuiTestCase() { secureSettings = FakeSettings() systemClock = FakeSystemClock() backgroundDelayableExecutor = FakeExecutor(systemClock) - whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState) + whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState) fontScalingDialogDelegate = spy( diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java index ebb6b48f9532..8895a5e1a97c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java @@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -118,7 +119,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mCachedDeviceManager); when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices); when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager); - when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState); + when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState); when(mCachedDevice.getAddress()).thenReturn(DEVICE_ADDRESS); when(mHearingDeviceItem.getCachedBluetoothDevice()).thenReturn(mCachedDevice); diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt index 41974f46ea7b..fd37cad72371 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt @@ -8,6 +8,7 @@ import android.content.pm.ApplicationInfo import android.graphics.Point import android.graphics.Rect import android.os.Looper +import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.IRemoteAnimationFinishedCallback @@ -17,15 +18,20 @@ import android.view.SurfaceControl import android.view.ViewGroup import android.widget.FrameLayout import android.widget.LinearLayout +import android.window.RemoteTransition +import android.window.TransitionFilter import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.shared.Flags import com.android.systemui.util.mockito.any +import com.android.wm.shell.shared.ShellTransitions import junit.framework.Assert.assertFalse import junit.framework.Assert.assertNotNull import junit.framework.Assert.assertNull import junit.framework.Assert.assertTrue import junit.framework.AssertionFailedError import kotlin.concurrent.thread +import kotlin.test.assertEquals import org.junit.After import org.junit.Assert.assertThrows import org.junit.Before @@ -46,7 +52,9 @@ import org.mockito.junit.MockitoJUnit @RunWithLooper class ActivityTransitionAnimatorTest : SysuiTestCase() { private val transitionContainer = LinearLayout(mContext) - private val testTransitionAnimator = fakeTransitionAnimator() + private val mainExecutor = context.mainExecutor + private val testTransitionAnimator = fakeTransitionAnimator(mainExecutor) + private val testShellTransitions = FakeShellTransitions() @Mock lateinit var callback: ActivityTransitionAnimator.Callback @Mock lateinit var listener: ActivityTransitionAnimator.Listener @Spy private val controller = TestTransitionAnimatorController(transitionContainer) @@ -54,14 +62,19 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { private lateinit var activityTransitionAnimator: ActivityTransitionAnimator @get:Rule val rule = MockitoJUnit.rule() + @get:Rule val setFlagsRule = SetFlagsRule() @Before fun setup() { activityTransitionAnimator = ActivityTransitionAnimator( + mainExecutor, + ActivityTransitionAnimator.TransitionRegister.fromShellTransitions( + testShellTransitions + ), testTransitionAnimator, testTransitionAnimator, - disableWmTimeout = true + disableWmTimeout = true, ) activityTransitionAnimator.callback = callback activityTransitionAnimator.addListener(listener) @@ -162,6 +175,34 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { } @Test + fun registersReturnIffCookieIsPresent() { + setFlagsRule.enableFlags(Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY) + `when`(callback.isOnKeyguard()).thenReturn(false) + + startIntentWithAnimation(activityTransitionAnimator, controller) { _ -> + ActivityManager.START_DELIVERED_TO_TOP + } + + waitForIdleSync() + assertTrue(testShellTransitions.remotes.isEmpty()) + assertTrue(testShellTransitions.remotesForTakeover.isEmpty()) + + val controller = + object : DelegateTransitionAnimatorController(controller) { + override val transitionCookie + get() = ActivityTransitionAnimator.TransitionCookie("testCookie") + } + + startIntentWithAnimation(activityTransitionAnimator, controller) { _ -> + ActivityManager.START_DELIVERED_TO_TOP + } + + waitForIdleSync() + assertEquals(1, testShellTransitions.remotes.size) + assertTrue(testShellTransitions.remotesForTakeover.isEmpty()) + } + + @Test fun doesNotStartIfAnimationIsCancelled() { val runner = activityTransitionAnimator.createRunner(controller) runner.onAnimationCancelled() @@ -241,6 +282,35 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() { } /** + * A fake implementation of [ShellTransitions] which saves filter-transition pairs locally and + * allows inspection. + */ +private class FakeShellTransitions : ShellTransitions { + val remotes = mutableMapOf<TransitionFilter, RemoteTransition>() + val remotesForTakeover = mutableMapOf<TransitionFilter, RemoteTransition>() + + override fun registerRemote(filter: TransitionFilter, remoteTransition: RemoteTransition) { + remotes[filter] = remoteTransition + } + + override fun registerRemoteForTakeover( + filter: TransitionFilter, + remoteTransition: RemoteTransition + ) { + remotesForTakeover[filter] = remoteTransition + } + + override fun unregisterRemote(remoteTransition: RemoteTransition) { + while (remotes.containsValue(remoteTransition)) { + remotes.values.remove(remoteTransition) + } + while (remotesForTakeover.containsValue(remoteTransition)) { + remotesForTakeover.values.remove(remoteTransition) + } + } +} + +/** * A simple implementation of [ActivityTransitionAnimator.Controller] which throws if it is called * outside of the main thread. */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt index d84a578294a3..e14762cd8792 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt @@ -156,6 +156,7 @@ class DialogTransitionAnimatorTest : SysuiTestCase() { fun testActivityLaunchWhenLockedWithoutAlternateAuth() { val dialogTransitionAnimator = fakeDialogTransitionAnimator( + mainExecutor = mContext.mainExecutor, isUnlocked = false, isShowingAlternateAuthOnUnlock = false, interactionJankMonitor = kosmos.interactionJankMonitor) @@ -166,6 +167,7 @@ class DialogTransitionAnimatorTest : SysuiTestCase() { @Test fun testActivityLaunchWhenLockedWithAlternateAuth() { val dialogTransitionAnimator = fakeDialogTransitionAnimator( + mainExecutor = mContext.mainExecutor, isUnlocked = false, isShowingAlternateAuthOnUnlock = true, interactionJankMonitor = kosmos.interactionJankMonitor diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt index b31fe21f8e91..42fcd547408a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt @@ -16,12 +16,16 @@ package com.android.systemui.animation +import android.os.HandlerThread import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import android.view.View import android.widget.FrameLayout import androidx.test.filters.SmallTest +import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.SysuiTestCase import com.android.systemui.animation.view.LaunchableFrameLayout +import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertThrows import org.junit.Test import org.junit.runner.RunWith @@ -30,6 +34,13 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper class GhostedViewTransitionAnimatorControllerTest : SysuiTestCase() { + companion object { + private const val LAUNCH_CUJ = 0 + private const val RETURN_CUJ = 1 + } + + private val interactionJankMonitor = FakeInteractionJankMonitor() + @Test fun animatingOrphanViewDoesNotCrash() { val state = TransitionAnimator.State(top = 0, bottom = 0, left = 0, right = 0) @@ -47,4 +58,63 @@ class GhostedViewTransitionAnimatorControllerTest : SysuiTestCase() { GhostedViewTransitionAnimatorController(FrameLayout(mContext)) } } + + @Test + fun cujsAreLoggedCorrectly() { + val parent = FrameLayout(mContext) + + val launchView = LaunchableFrameLayout(mContext) + parent.addView((launchView)) + val launchController = + GhostedViewTransitionAnimatorController( + launchView, + launchCujType = LAUNCH_CUJ, + returnCujType = RETURN_CUJ, + interactionJankMonitor = interactionJankMonitor + ) + launchController.onTransitionAnimationStart(isExpandingFullyAbove = true) + assertThat(interactionJankMonitor.ongoing).containsExactly(LAUNCH_CUJ) + launchController.onTransitionAnimationEnd(isExpandingFullyAbove = true) + assertThat(interactionJankMonitor.ongoing).isEmpty() + assertThat(interactionJankMonitor.finished).containsExactly(LAUNCH_CUJ) + + val returnView = LaunchableFrameLayout(mContext) + parent.addView((returnView)) + val returnController = + object : GhostedViewTransitionAnimatorController( + returnView, + launchCujType = LAUNCH_CUJ, + returnCujType = RETURN_CUJ, + interactionJankMonitor = interactionJankMonitor + ) { + override val isLaunching = false + } + returnController.onTransitionAnimationStart(isExpandingFullyAbove = true) + assertThat(interactionJankMonitor.ongoing).containsExactly(RETURN_CUJ) + returnController.onTransitionAnimationEnd(isExpandingFullyAbove = true) + assertThat(interactionJankMonitor.ongoing).isEmpty() + assertThat(interactionJankMonitor.finished).containsExactly(LAUNCH_CUJ, RETURN_CUJ) + } + + /** + * A fake implementation of [InteractionJankMonitor] which stores ongoing and finished CUJs and + * allows inspection. + */ + private class FakeInteractionJankMonitor : InteractionJankMonitor( + HandlerThread("testThread") + ) { + val ongoing: MutableSet<Int> = mutableSetOf() + val finished: MutableSet<Int> = mutableSetOf() + + override fun begin(v: View?, cujType: Int): Boolean { + ongoing.add(cujType) + return true + } + + override fun end(cujType: Int): Boolean { + ongoing.remove(cujType) + finished.add(cujType) + return true + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt index e64df905470d..259ece9fdf3f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt @@ -25,6 +25,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.activity.EmptyTestActivity +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.kosmos.Kosmos import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -58,9 +60,11 @@ class TransitionAnimatorTest : SysuiTestCase() { ) } + private val kosmos = Kosmos() private val pathManager = GoldenPathManager(context, GOLDENS_PATH, pathConfig = PathConfig()) private val transitionAnimator = TransitionAnimator( + kosmos.fakeExecutor, ActivityTransitionAnimator.TIMINGS, ActivityTransitionAnimator.INTERPOLATORS ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index de3b741b762c..e81369d9631c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -43,6 +43,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.jank.InteractionJankMonitor import com.android.internal.widget.LockPatternUtils +import com.android.launcher3.icons.IconProvider import com.android.systemui.Flags.FLAG_CONSTRAINT_BP import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository @@ -150,6 +151,7 @@ open class AuthContainerViewTest : SysuiTestCase() { private lateinit var displayStateInteractor: DisplayStateInteractor private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor private lateinit var biometricStatusInteractor: BiometricStatusInteractor + private lateinit var iconProvider: IconProvider private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor) private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android) @@ -178,6 +180,7 @@ open class AuthContainerViewTest : SysuiTestCase() { biometricStatusInteractor = BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository, fingerprintRepository) + iconProvider = IconProvider(context) // Set up default logo icon whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon) context.setMockPackageManager(packageManager) @@ -649,14 +652,15 @@ open class AuthContainerViewTest : SysuiTestCase() { lockPatternUtils, interactionJankMonitor, { promptSelectorInteractor }, - { bpCredentialInteractor }, PromptViewModel( displayStateInteractor, promptSelectorInteractor, context, udfpsOverlayInteractor, biometricStatusInteractor, - udfpsUtils + udfpsUtils, + iconProvider, + activityTaskManager ), { credentialViewModel }, Handler(TestableLooper.get(this).looper), diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt index df0e5a718ed9..5e4272f125d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt @@ -16,13 +16,8 @@ package com.android.systemui.biometrics.data.repository -import android.hardware.biometrics.BiometricManager -import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT -import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton import android.hardware.biometrics.PromptInfo -import android.hardware.biometrics.PromptVerticalListContentView import androidx.test.filters.SmallTest -import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.shared.model.PromptKind @@ -139,83 +134,6 @@ class PromptRepositoryImplTest : SysuiTestCase() { } @Test - fun showBpWithoutIconForCredential_withVerticalListContentView() = - testScope.runTest { - mSetFlagsRule.enableFlags(Flags.FLAG_CONSTRAINT_BP) - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - for (case in - listOf( - PromptKind.Biometric(), - PromptKind.Pin, - PromptKind.Password, - PromptKind.Pattern - )) { - val hasCredentialViewShown = case !is PromptKind.Biometric - val promptInfo = - PromptInfo().apply { - authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL - contentView = PromptVerticalListContentView.Builder().build() - } - repository.setPrompt(promptInfo, USER_ID, CHALLENGE, case, OP_PACKAGE_NAME) - repository.setShouldShowBpWithoutIconForCredential(promptInfo) - - assertThat(repository.showBpWithoutIconForCredential.value) - .isEqualTo(!hasCredentialViewShown) - } - } - - @Test - fun showBpWithoutIconForCredential_withContentViewWithMoreOptionsButton() = - testScope.runTest { - mSetFlagsRule.enableFlags(Flags.FLAG_CONSTRAINT_BP) - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - val promptInfo = - PromptInfo().apply { - authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL - contentView = - PromptContentViewWithMoreOptionsButton.Builder() - .setMoreOptionsButtonListener(fakeExecutor) { _, _ -> } - .build() - } - for (case in - listOf( - PromptKind.Biometric(), - PromptKind.Pin, - PromptKind.Password, - PromptKind.Pattern - )) { - repository.setPrompt(promptInfo, USER_ID, CHALLENGE, case, OP_PACKAGE_NAME) - repository.setShouldShowBpWithoutIconForCredential(promptInfo) - - assertThat(repository.showBpWithoutIconForCredential.value).isFalse() - } - } - - @Test - fun showBpWithoutIconForCredential_withDescription() = - testScope.runTest { - mSetFlagsRule.enableFlags(Flags.FLAG_CONSTRAINT_BP) - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - for (case in - listOf( - PromptKind.Biometric(), - PromptKind.Pin, - PromptKind.Password, - PromptKind.Pattern - )) { - val promptInfo = - PromptInfo().apply { - authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL - description = "description" - } - repository.setPrompt(promptInfo, USER_ID, CHALLENGE, case, OP_PACKAGE_NAME) - repository.setShouldShowBpWithoutIconForCredential(promptInfo) - - assertThat(repository.showBpWithoutIconForCredential.value).isFalse() - } - } - - @Test fun setsAndUnsetsPrompt() = testScope.runTest { val kind = PromptKind.Pin @@ -223,7 +141,7 @@ class PromptRepositoryImplTest : SysuiTestCase() { repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind, OP_PACKAGE_NAME) - assertThat(repository.kind.value).isEqualTo(kind) + assertThat(repository.promptKind.value).isEqualTo(kind) assertThat(repository.userId.value).isEqualTo(USER_ID) assertThat(repository.challenge.value).isEqualTo(CHALLENGE) assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt index 2172bc5ee8e1..8695c01e89d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt @@ -5,12 +5,12 @@ import android.hardware.biometrics.PromptInfo import android.hardware.biometrics.PromptVerticalListContentView import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.data.repository.FakePromptRepository import com.android.systemui.biometrics.domain.model.BiometricOperationInfo import com.android.systemui.biometrics.domain.model.BiometricPromptRequest import com.android.systemui.biometrics.promptInfo import com.android.systemui.biometrics.shared.model.BiometricUserInfo +import com.android.systemui.biometrics.shared.model.PromptKind import com.android.systemui.coroutines.collectLastValue import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock @@ -110,7 +110,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() { it.description = description it.subtitle = subtitle }, - kind = Utils.CREDENTIAL_PIN, + kind = PromptKind.Pin, userId = USER_ID, challenge = OPERATION_ID, opPackageName = OP_PACKAGE_NAME @@ -135,7 +135,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() { it.subtitle = subtitle it.contentView = contentView }, - kind = Utils.CREDENTIAL_PIN, + kind = PromptKind.Pin, userId = USER_ID, challenge = OPERATION_ID, opPackageName = OP_PACKAGE_NAME @@ -163,7 +163,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() { it.subtitle = subtitle it.contentView = contentView }, - kind = Utils.CREDENTIAL_PIN, + kind = PromptKind.Pin, userId = USER_ID, challenge = OPERATION_ID, opPackageName = OP_PACKAGE_NAME @@ -171,13 +171,13 @@ class PromptCredentialInteractorTest : SysuiTestCase() { assertThat(showTitleOnly).isFalse() } - @Test fun usePinCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PIN) + @Test fun usePinCredentialForPrompt() = useCredentialForPrompt(PromptKind.Pin) - @Test fun usePasswordCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PASSWORD) + @Test fun usePasswordCredentialForPrompt() = useCredentialForPrompt(PromptKind.Password) - @Test fun usePatternCredentialForPrompt() = useCredentialForPrompt(Utils.CREDENTIAL_PATTERN) + @Test fun usePatternCredentialForPrompt() = useCredentialForPrompt(PromptKind.Pattern) - private fun useCredentialForPrompt(kind: Int) = + private fun useCredentialForPrompt(kind: PromptKind) = testScope.runTest { val isStealth = false credentialInteractor.stealthMode = isStealth @@ -211,11 +211,10 @@ class PromptCredentialInteractorTest : SysuiTestCase() { assertThat(prompt) .isInstanceOf( when (kind) { - Utils.CREDENTIAL_PIN -> BiometricPromptRequest.Credential.Pin::class.java - Utils.CREDENTIAL_PASSWORD -> + PromptKind.Pin -> BiometricPromptRequest.Credential.Pin::class.java + PromptKind.Password -> BiometricPromptRequest.Credential.Password::class.java - Utils.CREDENTIAL_PATTERN -> - BiometricPromptRequest.Credential.Pattern::class.java + PromptKind.Pattern -> BiometricPromptRequest.Credential.Pattern::class.java else -> throw Exception("wrong kind") } ) @@ -341,6 +340,28 @@ class PromptCredentialInteractorTest : SysuiTestCase() { job.cancel() } + + /** Update the current request to use credential-based authentication instead of biometrics. */ + private fun PromptCredentialInteractor.useCredentialsForAuthentication( + promptInfo: PromptInfo, + kind: PromptKind, + userId: Int, + challenge: Long, + opPackageName: String, + ) { + biometricPromptRepository.setPrompt( + promptInfo, + userId, + challenge, + kind, + opPackageName, + ) + } + + /** Unset the current authentication request. */ + private fun PromptCredentialInteractor.resetPrompt() { + biometricPromptRepository.unsetPrompt() + } } private fun pinRequest(): BiometricPromptRequest.Credential.Pin = diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt index 2817780cbf03..4068404da29f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt @@ -17,12 +17,14 @@ package com.android.systemui.biometrics.domain.interactor import android.app.admin.DevicePolicyManager +import android.content.ComponentName import android.hardware.biometrics.BiometricManager.Authenticators +import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton import android.hardware.biometrics.PromptInfo +import android.hardware.biometrics.PromptVerticalListContentView import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository import com.android.systemui.biometrics.data.repository.FakePromptRepository import com.android.systemui.biometrics.faceSensorPropertiesInternal @@ -30,8 +32,10 @@ import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal import com.android.systemui.biometrics.shared.model.BiometricModalities import com.android.systemui.biometrics.shared.model.PromptKind import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope @@ -44,19 +48,22 @@ import org.junit.runners.JUnit4 import org.mockito.Mock import org.mockito.junit.MockitoJUnit -private const val TITLE = "hey there" -private const val SUBTITLE = "ok" -private const val DESCRIPTION = "football" -private const val NEGATIVE_TEXT = "escape" - -private const val USER_ID = 8 -private const val CHALLENGE = 999L -private const val OP_PACKAGE_NAME = "biometric.testapp" - @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(JUnit4::class) class PromptSelectorInteractorImplTest : SysuiTestCase() { + companion object { + private const val TITLE = "hey there" + private const val SUBTITLE = "ok" + private const val DESCRIPTION = "football" + private const val NEGATIVE_TEXT = "escape" + + private const val USER_ID = 8 + private const val CHALLENGE = 999L + private const val OP_PACKAGE_NAME = "biometric.testapp" + private val componentNameOverriddenForConfirmDeviceCredentialActivity = + ComponentName("not.com.android.settings", "testapp") + } @JvmField @Rule var mockitoRule = MockitoJUnit.rule() @@ -65,6 +72,7 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { private val testScope = TestScope() private val fingerprintRepository = FakeFingerprintPropertyRepository() private val promptRepository = FakePromptRepository() + private val fakeExecutor = FakeExecutor(FakeSystemClock()) private lateinit var interactor: PromptSelectorInteractor @@ -74,6 +82,23 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils) } + private fun basicPromptInfo() = + PromptInfo().apply { + title = TITLE + subtitle = SUBTITLE + description = DESCRIPTION + negativeButtonText = NEGATIVE_TEXT + isConfirmationRequested = true + isDeviceCredentialAllowed = true + authenticators = Authenticators.BIOMETRIC_STRONG or Authenticators.DEVICE_CREDENTIAL + } + + private val modalities = + BiometricModalities( + fingerprintProperties = fingerprintSensorPropertiesInternal().first(), + faceProperties = faceSensorPropertiesInternal().first(), + ) + @Test fun useBiometricsAndReset() = testScope.runTest { useBiometricsAndReset(allowCredentialFallback = true) } @@ -82,16 +107,24 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { fun useBiometricsAndResetWithoutFallback() = testScope.runTest { useBiometricsAndReset(allowCredentialFallback = false) } - private fun TestScope.useBiometricsAndReset(allowCredentialFallback: Boolean) { + @Test + fun useBiometricsAndResetOnConfirmDeviceCredentialActivity() = + testScope.runTest { + useBiometricsAndReset( + allowCredentialFallback = true, + setComponentNameForConfirmDeviceCredentialActivity = true + ) + } + + private fun TestScope.useBiometricsAndReset( + allowCredentialFallback: Boolean, + setComponentNameForConfirmDeviceCredentialActivity: Boolean = false + ) { setUserCredentialType(isPassword = true) val confirmationRequired = true val info = - PromptInfo().apply { - title = TITLE - subtitle = SUBTITLE - description = DESCRIPTION - negativeButtonText = NEGATIVE_TEXT + basicPromptInfo().apply { isConfirmationRequested = confirmationRequired authenticators = if (allowCredentialFallback) { @@ -100,26 +133,27 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { Authenticators.BIOMETRIC_STRONG } isDeviceCredentialAllowed = allowCredentialFallback + componentNameForConfirmDeviceCredentialActivity = + if (setComponentNameForConfirmDeviceCredentialActivity) + componentNameOverriddenForConfirmDeviceCredentialActivity + else null } - val modalities = - BiometricModalities( - fingerprintProperties = fingerprintSensorPropertiesInternal().first(), - faceProperties = faceSensorPropertiesInternal().first(), - ) val currentPrompt by collectLastValue(interactor.prompt) - val credentialKind by collectLastValue(interactor.credentialKind) + val promptKind by collectLastValue(interactor.promptKind) val isCredentialAllowed by collectLastValue(interactor.isCredentialAllowed) - val isExplicitConfirmationRequired by collectLastValue(interactor.isConfirmationRequired) + val credentialKind by collectLastValue(interactor.credentialKind) + val isConfirmationRequired by collectLastValue(interactor.isConfirmationRequired) assertThat(currentPrompt).isNull() - interactor.useBiometricsForAuthentication( + interactor.setPrompt( info, USER_ID, - CHALLENGE, modalities, - OP_PACKAGE_NAME + CHALLENGE, + OP_PACKAGE_NAME, + false /*onSwitchToCredential*/ ) assertThat(currentPrompt).isNotNull() @@ -128,36 +162,179 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { assertThat(currentPrompt?.subtitle).isEqualTo(SUBTITLE) assertThat(currentPrompt?.negativeButtonText).isEqualTo(NEGATIVE_TEXT) assertThat(currentPrompt?.opPackageName).isEqualTo(OP_PACKAGE_NAME) + assertThat(promptKind!!.isBiometric()).isTrue() + assertThat(currentPrompt?.componentNameForConfirmDeviceCredentialActivity) + .isEqualTo( + if (setComponentNameForConfirmDeviceCredentialActivity) + componentNameOverriddenForConfirmDeviceCredentialActivity + else null + ) if (allowCredentialFallback) { assertThat(credentialKind).isSameInstanceAs(PromptKind.Password) assertThat(isCredentialAllowed).isTrue() } else { - assertThat(credentialKind).isEqualTo(PromptKind.Biometric()) + assertThat(credentialKind).isEqualTo(PromptKind.None) assertThat(isCredentialAllowed).isFalse() } - assertThat(isExplicitConfirmationRequired).isEqualTo(confirmationRequired) + assertThat(isConfirmationRequired).isEqualTo(confirmationRequired) interactor.resetPrompt() verifyUnset() } @Test - fun usePinCredentialAndReset() = - testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PIN) } + fun usePinCredentialAndReset() = testScope.runTest { useCredentialAndReset(PromptKind.Pin) } @Test fun usePatternCredentialAndReset() = - testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PATTERN) } + testScope.runTest { useCredentialAndReset(PromptKind.Pattern) } @Test fun usePasswordCredentialAndReset() = - testScope.runTest { useCredentialAndReset(Utils.CREDENTIAL_PASSWORD) } + testScope.runTest { useCredentialAndReset(PromptKind.Password) } + + @Test + fun promptKind_isBiometric_whenBiometricAllowed() = + testScope.runTest { + setUserCredentialType(isPassword = true) + val info = basicPromptInfo() + + val promptKind by collectLastValue(interactor.promptKind) + assertThat(promptKind).isEqualTo(PromptKind.None) + + interactor.setPrompt( + info, + USER_ID, + modalities, + CHALLENGE, + OP_PACKAGE_NAME, + false /*onSwitchToCredential*/ + ) + + assertThat(promptKind?.isBiometric()).isTrue() + + interactor.resetPrompt() + verifyUnset() + } + + @Test + fun promptKind_isCredential_onSwitchToCredential() = + testScope.runTest { + setUserCredentialType(isPassword = true) + val info = basicPromptInfo() + + val promptKind by collectLastValue(interactor.promptKind) + assertThat(promptKind).isEqualTo(PromptKind.None) + + interactor.setPrompt( + info, + USER_ID, + modalities, + CHALLENGE, + OP_PACKAGE_NAME, + true /*onSwitchToCredential*/ + ) + + assertThat(promptKind).isEqualTo(PromptKind.Password) + + interactor.resetPrompt() + verifyUnset() + } - private fun TestScope.useCredentialAndReset(@Utils.CredentialType kind: Int) { + @Test + fun promptKind_isCredential_whenBiometricIsNotAllowed() = + testScope.runTest { + setUserCredentialType(isPassword = true) + val info = + basicPromptInfo().apply { + isDeviceCredentialAllowed = true + authenticators = Authenticators.DEVICE_CREDENTIAL + } + + val promptKind by collectLastValue(interactor.promptKind) + assertThat(promptKind).isEqualTo(PromptKind.None) + + interactor.setPrompt( + info, + USER_ID, + modalities, + CHALLENGE, + OP_PACKAGE_NAME, + false /*onSwitchToCredential*/ + ) + + assertThat(promptKind).isEqualTo(PromptKind.Password) + + interactor.resetPrompt() + verifyUnset() + } + + @Test + fun promptKind_isCredential_whenBiometricIsNotAllowed_withMoreOptionsButton() = + testScope.runTest { + setUserCredentialType(isPassword = true) + val info = + basicPromptInfo().apply { + isDeviceCredentialAllowed = true + authenticators = Authenticators.DEVICE_CREDENTIAL + contentView = + PromptContentViewWithMoreOptionsButton.Builder() + .setMoreOptionsButtonListener(fakeExecutor) { _, _ -> } + .build() + } + + val promptKind by collectLastValue(interactor.promptKind) + assertThat(promptKind).isEqualTo(PromptKind.None) + + interactor.setPrompt( + info, + USER_ID, + modalities, + CHALLENGE, + OP_PACKAGE_NAME, + false /*onSwitchToCredential*/ + ) + + assertThat(promptKind).isEqualTo(PromptKind.Password) + + interactor.resetPrompt() + verifyUnset() + } + + @Test + fun promptKind_isBiometric_whenBiometricIsNotAllowed_withVerticalList() = + testScope.runTest { + setUserCredentialType(isPassword = true) + val info = + basicPromptInfo().apply { + isDeviceCredentialAllowed = true + authenticators = Authenticators.DEVICE_CREDENTIAL + contentView = PromptVerticalListContentView.Builder().build() + } + + val promptKind by collectLastValue(interactor.promptKind) + assertThat(promptKind).isEqualTo(PromptKind.None) + + interactor.setPrompt( + info, + USER_ID, + modalities, + CHALLENGE, + OP_PACKAGE_NAME, + false /*onSwitchToCredential*/ + ) + + assertThat(promptKind?.isBiometric()).isTrue() + + interactor.resetPrompt() + verifyUnset() + } + + private fun TestScope.useCredentialAndReset(kind: PromptKind) { setUserCredentialType( - isPin = kind == Utils.CREDENTIAL_PIN, - isPassword = kind == Utils.CREDENTIAL_PASSWORD, + isPin = kind == PromptKind.Pin, + isPassword = kind == PromptKind.Password, ) val info = @@ -175,11 +352,18 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { assertThat(currentPrompt).isNull() - interactor.useCredentialsForAuthentication(info, kind, USER_ID, CHALLENGE, OP_PACKAGE_NAME) + interactor.setPrompt( + info, + USER_ID, + BiometricModalities(), + CHALLENGE, + OP_PACKAGE_NAME, + false /*onSwitchToCredential*/ + ) // not using biometrics, should be null with no fallback option assertThat(currentPrompt).isNull() - assertThat(credentialKind).isEqualTo(PromptKind.Biometric()) + assertThat(credentialKind).isEqualTo(PromptKind.None) interactor.resetPrompt() verifyUnset() @@ -187,13 +371,16 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { private fun TestScope.verifyUnset() { val currentPrompt by collectLastValue(interactor.prompt) + val promptKind by collectLastValue(interactor.promptKind) + val isCredentialAllowed by collectLastValue(interactor.isCredentialAllowed) val credentialKind by collectLastValue(interactor.credentialKind) + val isConfirmationRequired by collectLastValue(interactor.isConfirmationRequired) assertThat(currentPrompt).isNull() - - val kind = credentialKind as? PromptKind.Biometric - assertThat(kind).isNotNull() - assertThat(kind?.activeModalities?.isEmpty).isTrue() + assertThat(promptKind).isEqualTo(PromptKind.None) + assertThat(isCredentialAllowed).isFalse() + assertThat(credentialKind).isEqualTo(PromptKind.None) + assertThat(isConfirmationRequired).isFalse() } private fun setUserCredentialType(isPin: Boolean = false, isPassword: Boolean = false) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt index 5caa146f6351..0d01472b45c7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt @@ -16,97 +16,95 @@ package com.android.systemui.biometrics.ui.viewmodel +import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest -import com.android.systemui.SysUITestComponent -import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase -import com.android.systemui.TestMocksModule -import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule -import com.android.systemui.collectLastValue -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FakeFeatureFlagsClassicModule +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.BrokenWithSceneContainer import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.flags.parameterizeSceneContainerFlag +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.shared.model.StatusBarState -import com.android.systemui.runCurrent -import com.android.systemui.runTest -import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.kosmos.testScope +import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.shade.shadeTestUtil import com.android.systemui.statusbar.phone.SystemUIDialogManager -import com.android.systemui.user.domain.UserDomainLayerModule -import com.android.systemui.util.mockito.mock +import com.android.systemui.statusbar.phone.systemUIDialogManager +import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat -import dagger.BindsInstance -import dagger.Component import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(JUnit4::class) -class DefaultUdfpsTouchOverlayViewModelTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class DefaultUdfpsTouchOverlayViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { + + private val kosmos = + testKosmos().apply { + fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, true) } + } + private val testScope = kosmos.testScope + @Captor private lateinit var sysuiDialogListenerCaptor: ArgumentCaptor<SystemUIDialogManager.Listener> - private var systemUIDialogManager: SystemUIDialogManager = mock() + private var systemUIDialogManager = kosmos.systemUIDialogManager + private val keyguardRepository = kosmos.fakeKeyguardRepository + + private val shadeTestUtil by lazy { kosmos.shadeTestUtil } + + private lateinit var underTest: DefaultUdfpsTouchOverlayViewModel + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return parameterizeSceneContainerFlag() + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } @Before fun setUp() { MockitoAnnotations.initMocks(this) + underTest = + DefaultUdfpsTouchOverlayViewModel( + kosmos.shadeInteractor, + systemUIDialogManager, + ) } - @SysUISingleton - @Component( - modules = - [ - SysUITestModule::class, - UserDomainLayerModule::class, - BiometricsDomainLayerModule::class, - ] - ) - interface TestComponent : SysUITestComponent<DefaultUdfpsTouchOverlayViewModel> { - val keyguardRepository: FakeKeyguardRepository - val shadeRepository: FakeShadeRepository - @Component.Factory - interface Factory { - fun create( - @BindsInstance test: SysuiTestCase, - featureFlags: FakeFeatureFlagsClassicModule, - mocks: TestMocksModule, - ): TestComponent - } - } - - private fun TestComponent.shadeExpanded(expanded: Boolean) { + private fun shadeExpanded(expanded: Boolean) { if (expanded) { - shadeRepository.setLegacyShadeExpansion(1f) - shadeRepository.setLegacyShadeTracking(false) - shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(true) + shadeTestUtil.setShadeExpansion(1f) + shadeTestUtil.setTracking(false) + shadeTestUtil.setLegacyExpandedOrAwaitingInputTransfer(true) } else { keyguardRepository.setStatusBarState(StatusBarState.SHADE) - shadeRepository.setLegacyShadeExpansion(0f) - shadeRepository.setLegacyShadeTracking(false) - shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(false) + shadeTestUtil.setShadeExpansion(0f) + shadeTestUtil.setTracking(false) + shadeTestUtil.setLegacyExpandedOrAwaitingInputTransfer(false) } } - private val testComponent: TestComponent = - DaggerDefaultUdfpsTouchOverlayViewModelTest_TestComponent.factory() - .create( - test = this, - featureFlags = - FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }, - mocks = TestMocksModule(systemUIDialogManager = systemUIDialogManager), - ) - @Test + @BrokenWithSceneContainer(339465026) fun shadeNotExpanded_noDialogShowing_shouldHandleTouchesTrue() = - testComponent.runTest { + testScope.runTest { val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches) runCurrent() @@ -120,7 +118,7 @@ class DefaultUdfpsTouchOverlayViewModelTest : SysuiTestCase() { @Test fun shadeNotExpanded_dialogShowing_shouldHandleTouchesFalse() = - testComponent.runTest { + testScope.runTest { val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches) runCurrent() @@ -134,7 +132,7 @@ class DefaultUdfpsTouchOverlayViewModelTest : SysuiTestCase() { @Test fun shadeExpanded_noDialogShowing_shouldHandleTouchesFalse() = - testComponent.runTest { + testScope.runTest { val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index a7324182a91a..97c3c4264855 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -16,9 +16,13 @@ package com.android.systemui.biometrics.ui.viewmodel +import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityTaskManager +import android.content.ComponentName +import android.content.pm.ActivityInfo import android.content.pm.ApplicationInfo import android.content.pm.PackageManager +import android.content.pm.PackageManager.NameNotFoundException import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.Point @@ -37,6 +41,7 @@ import android.view.HapticFeedbackConstants import android.view.MotionEvent import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils +import com.android.launcher3.icons.IconProvider import com.android.systemui.Flags.FLAG_BP_TALKBACK import com.android.systemui.Flags.FLAG_CONSTRAINT_BP import com.android.systemui.SysuiTestCase @@ -94,6 +99,7 @@ private const val CHALLENGE = 2L private const val DELAY = 1000L private const val OP_PACKAGE_NAME = "biometric.testapp" private const val OP_PACKAGE_NAME_NO_ICON = "biometric.testapp.noicon" +private const val OP_PACKAGE_NAME_CAN_NOT_BE_FOUND = "can.not.be.found" @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -107,18 +113,23 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor @Mock private lateinit var udfpsUtils: UdfpsUtils @Mock private lateinit var packageManager: PackageManager + @Mock private lateinit var iconProvider: IconProvider @Mock private lateinit var applicationInfoWithIcon: ApplicationInfo @Mock private lateinit var applicationInfoNoIcon: ApplicationInfo @Mock private lateinit var activityTaskManager: ActivityTaskManager + @Mock private lateinit var activityInfo: ActivityInfo + @Mock private lateinit var runningTaskInfo: RunningTaskInfo private val fakeExecutor = FakeExecutor(FakeSystemClock()) private val testScope = TestScope() private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android) + private val defaultLogoIconWithOverrides = context.getDrawable(R.drawable.ic_add) private val logoResFromApp = R.drawable.ic_cake private val logoFromApp = context.getDrawable(logoResFromApp) private val logoBitmapFromApp = Bitmap.createBitmap(400, 400, Bitmap.Config.RGB_565) private val defaultLogoDescription = "Test Android App" private val logoDescriptionFromApp = "Test Cake App" + private val packageNameForLogoWithOverrides = "should.use.overridden.logo" private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository private lateinit var promptRepository: FakePromptRepository @@ -169,11 +180,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa ) biometricStatusRepository = FakeBiometricStatusRepository() biometricStatusInteractor = - BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository, - fingerprintRepository) - selector = - PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils) - selector.resetPrompt() + BiometricStatusInteractorImpl( + activityTaskManager, + biometricStatusRepository, + fingerprintRepository + ) + promptContentView = PromptVerticalListContentView.Builder() .addListItem(PromptContentItemBulletedText("content item 1")) @@ -183,32 +195,35 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa promptContentViewWithMoreOptionsButton = PromptContentViewWithMoreOptionsButton.Builder() .setDescription("test") - .setMoreOptionsButtonListener(fakeExecutor, { _, _ -> }) + .setMoreOptionsButtonListener(fakeExecutor) { _, _ -> } .build() - viewModel = - PromptViewModel( - displayStateInteractor, - selector, - mContext, - udfpsOverlayInteractor, - biometricStatusInteractor, - udfpsUtils - ) - iconViewModel = viewModel.iconViewModel - - // Set up default logo icon and app customized icon + // Set up default logo info and app customized info whenever(packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME_NO_ICON), anyInt())) .thenReturn(applicationInfoNoIcon) whenever(packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME), anyInt())) .thenReturn(applicationInfoWithIcon) + whenever(packageManager.getApplicationInfo(eq(packageNameForLogoWithOverrides), anyInt())) + .thenReturn(applicationInfoWithIcon) + whenever(packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME_CAN_NOT_BE_FOUND), anyInt())) + .thenThrow(NameNotFoundException()) + + whenever(packageManager.getActivityInfo(any(), anyInt())).thenReturn(activityInfo) + whenever(iconProvider.getIcon(activityInfo)).thenReturn(defaultLogoIconWithOverrides) whenever(packageManager.getApplicationIcon(applicationInfoWithIcon)) .thenReturn(defaultLogoIcon) whenever(packageManager.getApplicationLabel(applicationInfoWithIcon)) .thenReturn(defaultLogoDescription) + whenever(packageManager.getUserBadgedIcon(any(), any())).then { it.getArgument(0) } + whenever(packageManager.getUserBadgedLabel(any(), any())).then { it.getArgument(0) } + context.setMockPackageManager(packageManager) val resources = context.getOrCreateTestableResources() resources.addOverride(logoResFromApp, logoFromApp) + resources.addOverride( + R.array.biometric_dialog_package_names_for_logo_with_overrides, + arrayOf(packageNameForLogoWithOverrides) + ) } @Test @@ -1319,7 +1334,30 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - fun logoIsNullIfPackageNameNotFound() = + fun logo_nullIfPkgNameNotFound() = + runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) { + mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) + mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) + val logo by collectLastValue(viewModel.logo) + assertThat(logo).isNull() + } + + @Test + fun logo_defaultWithOverrides() = + runGenericTest(packageName = packageNameForLogoWithOverrides) { + mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) + mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) + val logo by collectLastValue(viewModel.logo) + + // 1. PM.getApplicationInfo(packageNameForLogoWithOverrides) is set to return + // applicationInfoWithIcon with defaultLogoIcon, + // 2. iconProvider.getIcon() is set to return defaultLogoIconForGMSCore + // For the apps with packageNameForLogoWithOverrides, 2 should be called instead of 1 + assertThat(logo).isEqualTo(defaultLogoIconWithOverrides) + } + + @Test + fun logo_defaultIsNull() = runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) { mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) @@ -1328,7 +1366,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - fun defaultLogoIfNoLogoSet() = runGenericTest { + fun logo_default() = runGenericTest { mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logo by collectLastValue(viewModel.logo) @@ -1336,7 +1374,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - fun logoResSetByApp() = + fun logo_resSetByApp() = runGenericTest(logoRes = logoResFromApp) { mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) @@ -1345,7 +1383,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - fun logoBitmapSetByApp() = + fun logo_bitmapSetByApp() = runGenericTest(logoBitmap = logoBitmapFromApp) { mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) @@ -1354,7 +1392,16 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - fun logoDescriptionIsEmptyIfPackageNameNotFound() = + fun logoDescription_emptyIfPkgNameNotFound() = + runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) { + mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) + mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) + val logoDescription by collectLastValue(viewModel.logoDescription) + assertThat(logoDescription).isEqualTo("") + } + + @Test + fun logoDescription_defaultIsEmpty() = runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) { mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) @@ -1363,7 +1410,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - fun defaultLogoDescriptionIfNoLogoDescriptionSet() = runGenericTest { + fun logoDescription_default() = runGenericTest { mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logoDescription by collectLastValue(viewModel.logoDescription) @@ -1371,7 +1418,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - fun logoDescriptionSetByApp() = + fun logoDescription_setByApp() = runGenericTest(logoDescription = logoDescriptionFromApp) { mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) @@ -1417,6 +1464,26 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa packageName: String = OP_PACKAGE_NAME, block: suspend TestScope.() -> Unit, ) { + val topActivity = ComponentName(packageName, "test app") + runningTaskInfo.topActivity = topActivity + whenever(activityTaskManager.getTasks(1)).thenReturn(listOf(runningTaskInfo)) + selector = + PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils) + selector.resetPrompt() + + viewModel = + PromptViewModel( + displayStateInteractor, + selector, + mContext, + udfpsOverlayInteractor, + biometricStatusInteractor, + udfpsUtils, + iconProvider, + activityTaskManager + ) + iconViewModel = viewModel.iconViewModel + selector.initializePrompt( requireConfirmation = testCase.confirmationRequested, allowCredentialFallback = allowCredentialFallback, @@ -1630,12 +1697,13 @@ private fun PromptSelectorInteractor.initializePrompt( isConfirmationRequested = requireConfirmation } - useBiometricsForAuthentication( + setPrompt( info, USER_ID, - CHALLENGE, BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face), + CHALLENGE, packageName, + false /*onUseDeviceCredential*/ ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java index a569ceec32b2..49f204372730 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java @@ -21,7 +21,7 @@ import static com.android.systemui.statusbar.phone.SystemUIDialog.DEFAULT_DISMIS import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.any; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -92,7 +92,7 @@ public class BroadcastDialogDelegateTest extends SysuiTestCase { when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager); when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null); - when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState); + when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState); when(mSystemUIDialogFactory.create(any(), any())).thenReturn(mDialog); mBroadcastDialogDelegate = new BroadcastDialogDelegate( diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt index 62c98b05cbd5..72156194b0e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt @@ -50,7 +50,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyBoolean -import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyLong import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnit @@ -104,7 +104,7 @@ class BluetoothTileDialogDelegateTest : SysuiTestCase() { dispatcher = UnconfinedTestDispatcher(scheduler) testScope = TestScope(dispatcher) - whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState) + whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState) mBluetoothTileDialogDelegate = BluetoothTileDialogDelegate( diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt index 28cbcb435223..4bcd9a9b3f1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.bluetooth.qsdialog import android.bluetooth.BluetoothDevice -import android.content.pm.PackageInfo +import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.media.AudioManager import android.platform.test.annotations.DisableFlags @@ -25,7 +25,6 @@ import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest -import com.android.settingslib.bluetooth.BluetoothUtils import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.flags.Flags import com.android.systemui.SysuiTestCase @@ -120,11 +119,10 @@ class DeviceItemFactoryTest : SysuiTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE) fun testSavedFactory_isFilterMatched_exclusivelyManaged_returnsFalse() { - val exclusiveManagerName = - BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)) - .thenReturn(exclusiveManagerName.toByteArray()) - `when`(packageManager.getPackageInfo(exclusiveManagerName, 0)).thenReturn(PackageInfo()) + .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray()) + `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0)) + .thenReturn(ApplicationInfo()) `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED) `when`(cachedDevice.isConnected).thenReturn(false) @@ -144,11 +142,11 @@ class DeviceItemFactoryTest : SysuiTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE) - fun testSavedFactory_isFilterMatched_notAllowedExclusiveManager_returnsTrue() { + fun testSavedFactory_isFilterMatched_exclusiveManagerNotEnabled_returnsTrue() { `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)) - .thenReturn(FAKE_EXCLUSIVE_MANAGER_NAME.toByteArray()) - `when`(packageManager.getPackageInfo(FAKE_EXCLUSIVE_MANAGER_NAME, 0)) - .thenReturn(PackageInfo()) + .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray()) + `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0)) + .thenReturn(ApplicationInfo().also { it.enabled = false }) `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED) `when`(cachedDevice.isConnected).thenReturn(false) @@ -158,12 +156,10 @@ class DeviceItemFactoryTest : SysuiTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE) - fun testSavedFactory_isFilterMatched_uninstalledExclusiveManager_returnsTrue() { - val exclusiveManagerName = - BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME + fun testSavedFactory_isFilterMatched_exclusiveManagerNotInstalled_returnsTrue() { `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)) - .thenReturn(exclusiveManagerName.toByteArray()) - `when`(packageManager.getPackageInfo(exclusiveManagerName, 0)) + .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray()) + `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0)) .thenThrow(PackageManager.NameNotFoundException("Test!")) `when`(cachedDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED) `when`(cachedDevice.isConnected).thenReturn(false) @@ -228,11 +224,10 @@ class DeviceItemFactoryTest : SysuiTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE) fun testConnectedFactory_isFilterMatched_exclusivelyManaged_returnsFalse() { - val exclusiveManagerName = - BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)) - .thenReturn(exclusiveManagerName.toByteArray()) - `when`(packageManager.getPackageInfo(exclusiveManagerName, 0)).thenReturn(PackageInfo()) + .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray()) + `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0)) + .thenReturn(ApplicationInfo()) `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED) `when`(bluetoothDevice.isConnected).thenReturn(true) audioManager.setMode(AudioManager.MODE_NORMAL) @@ -254,11 +249,11 @@ class DeviceItemFactoryTest : SysuiTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE) - fun testConnectedFactory_isFilterMatched_notAllowedExclusiveManager_returnsTrue() { + fun testConnectedFactory_isFilterMatched_exclusiveManagerNotEnabled_returnsTrue() { `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)) - .thenReturn(FAKE_EXCLUSIVE_MANAGER_NAME.toByteArray()) - `when`(packageManager.getPackageInfo(FAKE_EXCLUSIVE_MANAGER_NAME, 0)) - .thenReturn(PackageInfo()) + .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray()) + `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0)) + .thenReturn(ApplicationInfo().also { it.enabled = false }) `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED) `when`(bluetoothDevice.isConnected).thenReturn(true) audioManager.setMode(AudioManager.MODE_NORMAL) @@ -269,12 +264,10 @@ class DeviceItemFactoryTest : SysuiTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_HIDE_EXCLUSIVELY_MANAGED_BLUETOOTH_DEVICE) - fun testConnectedFactory_isFilterMatched_uninstalledExclusiveManager_returnsTrue() { - val exclusiveManagerName = - BluetoothUtils.getExclusiveManagers().firstOrNull() ?: FAKE_EXCLUSIVE_MANAGER_NAME + fun testConnectedFactory_isFilterMatched_exclusiveManagerNotInstalled_returnsTrue() { `when`(bluetoothDevice.getMetadata(BluetoothDevice.METADATA_EXCLUSIVE_MANAGER)) - .thenReturn(exclusiveManagerName.toByteArray()) - `when`(packageManager.getPackageInfo(exclusiveManagerName, 0)) + .thenReturn(TEST_EXCLUSIVE_MANAGER.toByteArray()) + `when`(packageManager.getApplicationInfo(TEST_EXCLUSIVE_MANAGER, 0)) .thenThrow(PackageManager.NameNotFoundException("Test!")) `when`(bluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED) `when`(bluetoothDevice.isConnected).thenReturn(true) @@ -317,7 +310,7 @@ class DeviceItemFactoryTest : SysuiTestCase() { companion object { const val DEVICE_NAME = "DeviceName" const val CONNECTION_SUMMARY = "ConnectionSummary" - private const val FAKE_EXCLUSIVE_MANAGER_NAME = "com.fake.name" + private const val TEST_EXCLUSIVE_MANAGER = "com.test.manager" private const val DEVICE_ADDRESS = "04:52:C7:0B:D8:3C" } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt index bed05ee811f1..cde7a0ed1bd4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/data/backup/CommunalBackupUtilsTest.kt @@ -30,7 +30,6 @@ import java.io.FileNotFoundException import java.nio.charset.Charset import org.junit.After import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -102,12 +101,6 @@ class CommunalBackupUtilsTest : SysuiTestCase() { assertThat(dataRead).isEqualTo(newDataToWrite) } - @Ignore("Ignored until we figure out why it is flaky b/336561027") - @Test(expected = FileNotFoundException::class) - fun read_fileNotFoundException() { - underTest.readBytesFromDisk() - } - @Test(expected = FileNotFoundException::class) fun clear_returnsTrueWhenFileDeleted() { // Write bytes to disk diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt index 6a0462b72544..c39c3fe5f527 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.deviceentry.domain.ui.viewmodel -import androidx.test.ext.junit.runners.AndroidJUnit4 +import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.accessibility.data.repository.fakeAccessibilityRepository @@ -24,7 +24,9 @@ import com.android.systemui.biometrics.data.repository.fingerprintPropertyReposi import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.data.ui.viewmodel.deviceEntryUdfpsAccessibilityOverlayViewModel +import com.android.systemui.deviceentry.ui.viewmodel.DeviceEntryUdfpsAccessibilityOverlayViewModel import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER +import com.android.systemui.flags.andSceneContainer import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository @@ -34,19 +36,22 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.viewmodel.fakeDeviceEntryIconViewModelTransition import com.android.systemui.kosmos.testScope -import com.android.systemui.shade.data.repository.fakeShadeRepository +import com.android.systemui.shade.shadeTestUtil import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.runner.RunWith +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @ExperimentalCoroutinesApi @SmallTest -@RunWith(AndroidJUnit4::class) -class UdfpsAccessibilityOverlayViewModelTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class UdfpsAccessibilityOverlayViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos().apply { fakeFeatureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) } @@ -59,8 +64,27 @@ class UdfpsAccessibilityOverlayViewModelTest : SysuiTestCase() { private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository private val deviceEntryFingerprintAuthRepository = kosmos.deviceEntryFingerprintAuthRepository private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository - private val shadeRepository = kosmos.fakeShadeRepository - private val underTest = kosmos.deviceEntryUdfpsAccessibilityOverlayViewModel + + private val shadeTestUtil by lazy { kosmos.shadeTestUtil } + + private lateinit var underTest: DeviceEntryUdfpsAccessibilityOverlayViewModel + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf().andSceneContainer() + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + + @Before + fun setup() { + underTest = kosmos.deviceEntryUdfpsAccessibilityOverlayViewModel + } @Test fun visible() = @@ -142,7 +166,7 @@ class UdfpsAccessibilityOverlayViewModelTest : SysuiTestCase() { ) // Shade not expanded - shadeRepository.qsExpansion.value = 0f - shadeRepository.lockscreenShadeExpansion.value = 0f + shadeTestUtil.setQsExpansion(0f) + shadeTestUtil.setLockscreenShadeExpansion(0f) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 1dc58d1784d7..ef15d21107b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -17,6 +17,8 @@ package com.android.systemui.keyguard.domain.interactor import android.app.StatusBarManager +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.keyguard.KeyguardSecurityModel @@ -29,7 +31,10 @@ import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dock.fakeDockManager +import com.android.systemui.flags.BrokenWithSceneContainer +import com.android.systemui.flags.DisableSceneContainer import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.andSceneContainer import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeCommandQueue import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository @@ -46,7 +51,8 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.powerInteractor -import com.android.systemui.shade.data.repository.fakeShadeRepository +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.shade.shadeTestUtil import com.android.systemui.statusbar.commandQueue import com.android.systemui.testKosmos import com.android.systemui.util.mockito.whenever @@ -61,13 +67,14 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.reset import org.mockito.Mockito.spy import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters /** * Class for testing user journeys through the interactors. They will all be activated during setup, @@ -75,8 +82,8 @@ import org.mockito.MockitoAnnotations */ @ExperimentalCoroutinesApi @SmallTest -@RunWith(JUnit4::class) -class KeyguardTransitionScenariosTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTestCase() { private val kosmos = testKosmos().apply { fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository()) @@ -87,7 +94,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { private val keyguardRepository = kosmos.fakeKeyguardRepository private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository private var commandQueue = kosmos.fakeCommandQueue - private val shadeRepository = kosmos.fakeShadeRepository + private val shadeTestUtil by lazy { kosmos.shadeTestUtil } private val transitionRepository = kosmos.fakeKeyguardTransitionRepository private lateinit var featureFlags: FakeFeatureFlags @@ -112,6 +119,18 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { private val communalInteractor = kosmos.communalInteractor private val dockManager = kosmos.fakeDockManager + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf().andSceneContainer() + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags!!) + } + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -119,9 +138,11 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN) mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB) - mSetFlagsRule.disableFlags( - Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR, - ) + if (!SceneContainerFlag.isEnabled) { + mSetFlagsRule.disableFlags( + Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR, + ) + } featureFlags = FakeFeatureFlags() fromLockscreenTransitionInteractor.start() @@ -137,6 +158,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun lockscreenToPrimaryBouncerViaBouncerShowingCall() = testScope.runTest { // GIVEN a prior transition has run to LOCKSCREEN @@ -210,6 +232,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun lockscreenToDreaming() = testScope.runTest { // GIVEN a device that is not dreaming or dozing @@ -238,6 +261,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun lockscreenToDreamingLockscreenHosted() = testScope.runTest { // GIVEN a device that is not dreaming or dozing @@ -348,6 +372,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun dreamingLockscreenHostedToGone() = testScope.runTest { // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED @@ -374,6 +399,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun dreamingLockscreenHostedToPrimaryBouncer() = testScope.runTest { // GIVEN a device dreaming with lockscreen hosted dream and not dozing @@ -527,6 +553,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun dozingToGoneWithUnlock() = testScope.runTest { // GIVEN a prior transition has run to DOZING @@ -600,6 +627,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { /** This handles security method NONE and screen off with lock timeout */ @Test + @DisableSceneContainer fun dreamingToGoneWithKeyguardNotShowing() = testScope.runTest { // GIVEN a prior transition has run to DREAMING @@ -656,6 +684,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun goneToDozing() = testScope.runTest { // GIVEN a device with AOD not available @@ -681,6 +710,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun goneToAod() = testScope.runTest { // GIVEN a device with AOD available @@ -706,6 +736,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun goneToLockscreen() = testScope.runTest { // GIVEN a prior transition has run to GONE @@ -727,6 +758,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun goneToDreaming() = testScope.runTest { // GIVEN a device that is not dreaming or dozing @@ -755,6 +787,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun goneToGlanceableHub() = testScope.runTest { // GIVEN a prior transition has run to GONE @@ -784,6 +817,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun alternateBouncerToPrimaryBouncer() = testScope.runTest { // GIVEN a prior transition has run to ALTERNATE_BOUNCER @@ -897,6 +931,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun alternateBouncerToGone() = testScope.runTest { // GIVEN a prior transition has run to ALTERNATE_BOUNCER @@ -959,6 +994,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun primaryBouncerToAod() = testScope.runTest { // GIVEN aod available @@ -989,6 +1025,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun primaryBouncerToDozing() = testScope.runTest { // GIVEN a prior transition has run to PRIMARY_BOUNCER @@ -1017,6 +1054,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun primaryBouncerToLockscreen() = testScope.runTest { // GIVEN a prior transition has run to PRIMARY_BOUNCER @@ -1040,6 +1078,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun primaryBouncerToGlanceableHub() = testScope.runTest { // GIVEN a prior transition has run to PRIMARY_BOUNCER @@ -1071,6 +1110,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun primaryBouncerToGlanceableHubWhileDreaming() = testScope.runTest { // GIVEN a prior transition has run to PRIMARY_BOUNCER @@ -1106,6 +1146,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun primaryBouncerToDreamingLockscreenHosted() = testScope.runTest { // GIVEN device dreaming with the lockscreen hosted dream and not dozing @@ -1135,6 +1176,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun occludedToGone() = testScope.runTest { // GIVEN a device on lockscreen @@ -1165,6 +1207,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun occludedToLockscreen() = testScope.runTest { // GIVEN a device on lockscreen @@ -1193,6 +1236,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun occludedToGlanceableHub() = testScope.runTest { // GIVEN a device on lockscreen @@ -1229,6 +1273,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun occludedToGlanceableHubWhenInitiallyOnHub() = testScope.runTest { // GIVEN a device on lockscreen and communal is available @@ -1314,6 +1359,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun primaryBouncerToOccluded() = testScope.runTest { // GIVEN a prior transition has run to PRIMARY_BOUNCER @@ -1339,6 +1385,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun dozingToOccluded() = testScope.runTest { // GIVEN a prior transition has run to DOZING @@ -1364,6 +1411,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun dreamingToOccluded() = testScope.runTest { // GIVEN a prior transition has run to DREAMING @@ -1392,6 +1440,39 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) + @EnableFlags(Flags.FLAG_RESTART_DREAM_ON_UNOCCLUDE) + fun dreamingToOccludedToDreaming() = + testScope.runTest { + // GIVEN a device on lockscreen + keyguardRepository.setKeyguardShowing(true) + runCurrent() + + // Given a device that is dreaming + keyguardRepository.setDreaming(true) + + // GIVEN a prior transition has run to OCCLUDED + runTransitionAndSetWakefulness(KeyguardState.DREAMING, KeyguardState.OCCLUDED) + keyguardRepository.setKeyguardOccluded(true) + runCurrent() + + // WHEN occlusion ends + keyguardRepository.setKeyguardOccluded(false) + runCurrent() + + // THEN a transition to DREAMING should occur + assertThat(transitionRepository) + .startedTransition( + ownerName = FromOccludedTransitionInteractor::class.simpleName, + from = KeyguardState.OCCLUDED, + to = KeyguardState.DREAMING, + animatorAssertion = { it.isNotNull() }, + ) + + coroutineContext.cancelChildren() + } + + @Test fun dreamingToPrimaryBouncer() = testScope.runTest { // GIVEN a prior transition has run to DREAMING @@ -1445,6 +1526,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun dreamingToGlanceableHub() = testScope.runTest { // GIVEN a prior transition has run to DREAMING @@ -1484,6 +1566,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun lockscreenToOccluded() = testScope.runTest { // GIVEN a prior transition has run to LOCKSCREEN @@ -1507,6 +1590,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun aodToOccluded() = testScope.runTest { // GIVEN a prior transition has run to AOD @@ -1530,6 +1614,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun aodToPrimaryBouncer() = testScope.runTest { // GIVEN a prior transition has run to AOD @@ -1553,6 +1638,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun lockscreenToOccluded_fromCameraGesture() = testScope.runTest { // GIVEN a prior transition has run to LOCKSCREEN @@ -1586,6 +1672,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun lockscreenToPrimaryBouncerDragging() = testScope.runTest { // GIVEN a prior transition has run to LOCKSCREEN @@ -1595,8 +1682,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN the keyguard is showing locked keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) runCurrent() - shadeRepository.setLegacyShadeTracking(true) - shadeRepository.setLegacyShadeExpansion(.9f) + shadeTestUtil.setTracking(true) + shadeTestUtil.setShadeExpansion(.9f) runCurrent() // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur @@ -1613,8 +1700,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // WHEN the user stops dragging and shade is back to expanded clearInvocations(transitionRepository) runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER) - shadeRepository.setLegacyShadeTracking(false) - shadeRepository.setLegacyShadeExpansion(1f) + shadeTestUtil.setTracking(false) + shadeTestUtil.setShadeExpansion(1f) runCurrent() // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur @@ -1629,6 +1716,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun lockscreenToGlanceableHub() = testScope.runTest { // GIVEN a prior transition has run to LOCKSCREEN @@ -1686,6 +1774,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun glanceableHubToLockscreen() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB @@ -1740,6 +1829,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun glanceableHubToDozing() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB @@ -1761,6 +1851,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun glanceableHubToPrimaryBouncer() = testScope.runTest { // GIVEN a prior transition has run to ALTERNATE_BOUNCER @@ -1782,6 +1873,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun glanceableHubToAlternateBouncer() = testScope.runTest { // GIVEN a prior transition has run to ALTERNATE_BOUNCER @@ -1803,6 +1895,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun glanceableHubToOccluded() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB @@ -1833,6 +1926,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun glanceableHubToGone() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB @@ -1854,6 +1948,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun glanceableHubToDreaming() = testScope.runTest { // GIVEN that we are dreaming and not dozing diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt index b1a8dd1d3fdc..a77169e74de5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt @@ -18,20 +18,29 @@ package com.android.systemui.keyguard.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.data.repository.sceneContainerRepository +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat import junit.framework.Assert.assertEquals import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -57,14 +66,22 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { .thenReturn(surfaceBehindIsAnimatingFlow) } - private val underTest = kosmos.windowManagerLockscreenVisibilityInteractor + private val underTest = lazy { kosmos.windowManagerLockscreenVisibilityInteractor } private val testScope = kosmos.testScope private val transitionRepository = kosmos.fakeKeyguardTransitionRepository + @Before + fun setUp() { + // lazy value needs to be called here otherwise flow collection misbehaves + underTest.value + kosmos.sceneContainerRepository.setTransitionState(sceneTransitions) + } + @Test + @DisableSceneContainer fun surfaceBehindVisibility_switchesToCorrectFlow() = testScope.runTest { - val values by collectValues(underTest.surfaceBehindVisibility) + val values by collectValues(underTest.value.surfaceBehindVisibility) // Start on LOCKSCREEN. transitionRepository.sendTransitionStep( @@ -170,9 +187,10 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun testUsingGoingAwayAnimation_duringTransitionToGone() = testScope.runTest { - val values by collectValues(underTest.usingKeyguardGoingAwayAnimation) + val values by collectValues(underTest.value.usingKeyguardGoingAwayAnimation) // Start on LOCKSCREEN. transitionRepository.sendTransitionStep( @@ -230,9 +248,10 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun testNotUsingGoingAwayAnimation_evenWhenAnimating_ifStateIsNotGone() = testScope.runTest { - val values by collectValues(underTest.usingKeyguardGoingAwayAnimation) + val values by collectValues(underTest.value.usingKeyguardGoingAwayAnimation) // Start on LOCKSCREEN. transitionRepository.sendTransitionStep( @@ -319,9 +338,10 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun lockscreenVisibility_visibleWhenGone() = testScope.runTest { - val values by collectValues(underTest.lockscreenVisibility) + val values by collectValues(underTest.value.lockscreenVisibility) // Start on LOCKSCREEN. transitionRepository.sendTransitionStep( @@ -385,9 +405,10 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun testLockscreenVisibility_usesFromState_ifCanceled() = testScope.runTest { - val values by collectValues(underTest.lockscreenVisibility) + val values by collectValues(underTest.value.lockscreenVisibility) transitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, @@ -486,9 +507,10 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { * state during the AOD/isAsleep -> GONE transition is AOD (where lockscreen visibility = true). */ @Test + @DisableSceneContainer fun testLockscreenVisibility_falseDuringTransitionToGone_fromCanceledGone() = testScope.runTest { - val values by collectValues(underTest.lockscreenVisibility) + val values by collectValues(underTest.value.lockscreenVisibility) transitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, @@ -587,11 +609,11 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { ) } - /** */ @Test + @DisableSceneContainer fun testLockscreenVisibility_trueDuringTransitionToGone_fromNotCanceledGone() = testScope.runTest { - val values by collectValues(underTest.lockscreenVisibility) + val values by collectValues(underTest.value.lockscreenVisibility) transitionRepository.sendTransitionSteps( from = KeyguardState.LOCKSCREEN, @@ -702,4 +724,109 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { values ) } + + @Test + @EnableSceneContainer + fun sceneContainer_lockscreenVisibility_visibleWhenNotGone() = + testScope.runTest { + val lockscreenVisibility by collectLastValue(underTest.value.lockscreenVisibility) + + sceneTransitions.value = lsToGone + assertThat(lockscreenVisibility).isTrue() + + sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone) + assertThat(lockscreenVisibility).isFalse() + + sceneTransitions.value = goneToLs + assertThat(lockscreenVisibility).isFalse() + + sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen) + assertThat(lockscreenVisibility).isTrue() + } + + @Test + @EnableSceneContainer + fun sceneContainer_lockscreenVisibility_notVisibleWhenReturningToGone() = + testScope.runTest { + val lockscreenVisibility by collectLastValue(underTest.value.lockscreenVisibility) + + sceneTransitions.value = goneToLs + assertThat(lockscreenVisibility).isFalse() + + sceneTransitions.value = lsToGone + assertThat(lockscreenVisibility).isFalse() + + sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone) + assertThat(lockscreenVisibility).isFalse() + + sceneTransitions.value = goneToLs + assertThat(lockscreenVisibility).isFalse() + + sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen) + assertThat(lockscreenVisibility).isTrue() + } + + @Test + @EnableSceneContainer + fun sceneContainer_usingGoingAwayAnimation_duringTransitionToGone() = + testScope.runTest { + val usingKeyguardGoingAwayAnimation by + collectLastValue(underTest.value.usingKeyguardGoingAwayAnimation) + + sceneTransitions.value = lsToGone + assertThat(usingKeyguardGoingAwayAnimation).isTrue() + + sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone) + assertThat(usingKeyguardGoingAwayAnimation).isFalse() + } + + @Test + @EnableSceneContainer + fun sceneContainer_usingGoingAwayAnimation_surfaceBehindIsAnimating() = + testScope.runTest { + val usingKeyguardGoingAwayAnimation by + collectLastValue(underTest.value.usingKeyguardGoingAwayAnimation) + + sceneTransitions.value = lsToGone + surfaceBehindIsAnimatingFlow.emit(true) + assertThat(usingKeyguardGoingAwayAnimation).isTrue() + + sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone) + assertThat(usingKeyguardGoingAwayAnimation).isTrue() + + sceneTransitions.value = goneToLs + assertThat(usingKeyguardGoingAwayAnimation).isTrue() + + surfaceBehindIsAnimatingFlow.emit(false) + assertThat(usingKeyguardGoingAwayAnimation).isFalse() + } + + companion object { + private val progress = MutableStateFlow(0f) + + private val sceneTransitions = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(Scenes.Lockscreen) + ) + + private val lsToGone = + ObservableTransitionState.Transition( + Scenes.Lockscreen, + Scenes.Gone, + flowOf(Scenes.Lockscreen), + progress, + false, + flowOf(false) + ) + + private val goneToLs = + ObservableTransitionState.Transition( + Scenes.Gone, + Scenes.Lockscreen, + flowOf(Scenes.Lockscreen), + progress, + false, + flowOf(false) + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt new file mode 100644 index 000000000000..d0d9891a953f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt @@ -0,0 +1,1320 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor.scenetransition + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues +import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.realKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionInfo +import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.data.repository.sceneContainerRepository +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Ignore +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class LockscreenSceneTransitionInteractorTest : SysuiTestCase() { + private val kosmos = + testKosmos().apply { keyguardTransitionRepository = realKeyguardTransitionRepository } + + private val testScope = kosmos.testScope + private val underTest = kosmos.lockscreenSceneTransitionInteractor + + private val progress = MutableStateFlow(0f) + + private val sceneTransitions = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(Scenes.Lockscreen) + ) + + private val lsToGone = + ObservableTransitionState.Transition( + Scenes.Lockscreen, + Scenes.Gone, + flowOf(Scenes.Lockscreen), + progress, + false, + flowOf(false) + ) + + private val goneToLs = + ObservableTransitionState.Transition( + Scenes.Gone, + Scenes.Lockscreen, + flowOf(Scenes.Lockscreen), + progress, + false, + flowOf(false) + ) + + @Before + fun setUp() { + underTest.start() + kosmos.sceneContainerRepository.setTransitionState(sceneTransitions) + testScope.launch { + kosmos.realKeyguardTransitionRepository.emitInitialStepsFromOff( + KeyguardState.LOCKSCREEN + ) + } + } + + /** STL: Ls -> Gone, then settle with Idle(Gone). This is the default case. */ + @Test + fun transition_from_ls_scene_end_in_gone() = + testScope.runTest { + sceneTransitions.value = lsToGone + + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.RUNNING, + progress = 0f, + ) + + progress.value = 0.4f + assertTransition( + step = currentStep!!, + state = TransitionState.RUNNING, + progress = 0.4f, + ) + + sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone) + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.FINISHED, + progress = 1f, + ) + } + + /** + * STL: Ls -> Gone, then settle with Idle(Ls). KTF in this scenario needs to invert the + * transition LS -> UNDEFINED to UNDEFINED -> LS as there is no mechanism in KTF to + * finish/settle to progress 0.0f. + */ + @Test + fun transition_from_ls_scene_end_in_ls() = + testScope.runTest { + sceneTransitions.value = lsToGone + + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions) + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.RUNNING, + progress = 0f, + ) + + progress.value = 0.4f + assertTransition( + step = currentStep!!, + state = TransitionState.RUNNING, + progress = 0.4f, + ) + + sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen) + + assertTransition( + step = allSteps[allSteps.size - 3], + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.CANCELED, + progress = 0.4f, + ) + + assertTransition( + step = allSteps[allSteps.size - 2], + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.STARTED, + progress = 0.6f, + ) + + assertTransition( + step = allSteps[allSteps.size - 1], + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.FINISHED, + progress = 1f, + ) + } + + /** + * STL: Ls -> Gone, then settle with Idle(Ls). KTF starts in AOD and needs to inverse correctly + * back to AOD. + */ + @Test + fun transition_from_ls_scene_on_aod_end_in_ls() = + testScope.runTest { + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions) + + kosmos.realKeyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = this.javaClass.simpleName, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + animator = null, + modeOnCanceled = TransitionModeOnCanceled.RESET + ) + ) + sceneTransitions.value = lsToGone + + assertTransition( + step = currentStep!!, + from = KeyguardState.AOD, + to = KeyguardState.UNDEFINED, + state = TransitionState.RUNNING, + progress = 0f, + ) + + progress.value = 0.4f + assertTransition( + step = currentStep!!, + state = TransitionState.RUNNING, + progress = 0.4f, + ) + + sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen) + + assertTransition( + step = allSteps[allSteps.size - 3], + from = KeyguardState.AOD, + to = KeyguardState.UNDEFINED, + state = TransitionState.CANCELED, + progress = 0.4f, + ) + + assertTransition( + step = allSteps[allSteps.size - 2], + from = KeyguardState.UNDEFINED, + to = KeyguardState.AOD, + state = TransitionState.STARTED, + progress = 0.6f, + ) + + assertTransition( + step = allSteps[allSteps.size - 1], + from = KeyguardState.UNDEFINED, + to = KeyguardState.AOD, + state = TransitionState.FINISHED, + progress = 1f, + ) + } + + /** + * STL: Gone -> Ls, then settle with Idle(Ls). This is the default case in the reverse + * direction. + */ + @Test + fun transition_to_ls_scene_end_in_ls() = + testScope.runTest { + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + sceneTransitions.value = goneToLs + + assertTransition( + step = currentStep!!, + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.RUNNING, + progress = 0f, + ) + + progress.value = 0.4f + assertTransition( + step = currentStep!!, + state = TransitionState.RUNNING, + progress = 0.4f, + ) + + sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen) + + assertTransition( + step = currentStep!!, + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.FINISHED, + progress = 1f, + ) + } + + /** STL: Gone -> Ls (AOD), will transition to AOD once */ + @Test + fun transition_to_ls_scene_with_changed_next_scene_is_respected_just_once() = + testScope.runTest { + underTest.onSceneAboutToChange(Scenes.Lockscreen, KeyguardState.AOD) + sceneTransitions.value = goneToLs + + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + assertTransition( + step = currentStep!!, + from = KeyguardState.UNDEFINED, + to = KeyguardState.AOD, + state = TransitionState.RUNNING, + progress = 0f, + ) + + sceneTransitions.value = + ObservableTransitionState.Transition( + Scenes.Shade, + Scenes.Lockscreen, + flowOf(Scenes.Lockscreen), + progress, + false, + flowOf(false) + ) + + assertTransition( + step = currentStep!!, + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.RUNNING, + progress = 0f, + ) + } + + /** + * STL: Gone -> Ls, then settle with Idle(Gone). KTF in this scenario needs to invert the + * transition UNDEFINED -> LS to LS -> UNDEFINED as there is no mechanism in KTF to + * finish/settle to progress 0.0f. + */ + @Test + fun transition_to_ls_scene_end_in_from_scene() = + testScope.runTest { + sceneTransitions.value = goneToLs + + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions) + assertTransition( + step = currentStep!!, + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.RUNNING, + progress = 0f, + ) + + progress.value = 0.4f + assertTransition( + step = currentStep!!, + state = TransitionState.RUNNING, + progress = 0.4f, + ) + + sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone) + val stepM3 = allSteps[allSteps.size - 3] + val stepM2 = allSteps[allSteps.size - 2] + + assertTransition( + step = stepM3, + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.CANCELED, + progress = 0.4f, + ) + + assertTransition( + step = stepM2, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.STARTED, + progress = 0.6f, + ) + + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.FINISHED, + progress = 1f, + ) + } + + /** + * STL: Gone -> Ls, then interrupted by Shade -> Ls. KTF in this scenario needs to invert the + * transition UNDEFINED -> LS to LS -> UNDEFINED as there is no mechanism in KTF to + * finish/settle to progress 0.0f. Then restart a different transition UNDEFINED -> Ls. + */ + @Test + fun transition_to_ls_scene_end_in_to_ls_transition() = + testScope.runTest { + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions) + sceneTransitions.value = goneToLs + progress.value = 0.4f + + assertTransition( + step = currentStep!!, + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.RUNNING, + progress = 0.4f, + ) + + sceneTransitions.value = + ObservableTransitionState.Transition( + Scenes.Shade, + Scenes.Lockscreen, + flowOf(Scenes.Lockscreen), + progress, + false, + flowOf(false) + ) + + assertTransition( + step = allSteps[allSteps.size - 5], + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.CANCELED, + progress = 0.4f, + ) + + assertTransition( + step = allSteps[allSteps.size - 4], + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.STARTED, + progress = 0.6f, + ) + + assertTransition( + step = allSteps[allSteps.size - 3], + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.FINISHED, + progress = 1f, + ) + + assertTransition( + step = allSteps[allSteps.size - 2], + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.STARTED, + progress = 0f, + ) + + progress.value = 0.2f + assertTransition( + step = allSteps[allSteps.size - 1], + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.RUNNING, + progress = 0.2f, + ) + } + + /** + * STL: Gone -> Ls, then interrupted by Ls -> Shade. This is like continuing the transition from + * Ls before the transition before has properly settled. This can happen in STL e.g. with an + * accelerated swipe (quick successive fling gestures). + */ + @Test + fun transition_to_ls_scene_end_in_from_ls_transition() = + testScope.runTest { + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions) + sceneTransitions.value = goneToLs + progress.value = 0.4f + + assertTransition( + step = currentStep!!, + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.RUNNING, + progress = 0.4f, + ) + + sceneTransitions.value = + ObservableTransitionState.Transition( + Scenes.Lockscreen, + Scenes.Shade, + flowOf(Scenes.Lockscreen), + progress, + false, + flowOf(false) + ) + + assertTransition( + step = allSteps[allSteps.size - 3], + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.CANCELED, + progress = 0.4f, + ) + + assertTransition( + step = allSteps[allSteps.size - 2], + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.STARTED, + progress = 0.0f, + ) + + progress.value = 0.2f + assertTransition( + step = allSteps[allSteps.size - 1], + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.RUNNING, + progress = 0.2f, + ) + } + + /** + * STL: Gone -> Ls, then interrupted by Gone -> Shade. This is going back to Gone but starting a + * transition from Gone before settling in Gone. KTF needs to make sure the transition is + * properly inversed and settled in UNDEFINED. + */ + @Test + fun transition_to_ls_scene_end_in_other_transition() = + testScope.runTest { + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions) + sceneTransitions.value = goneToLs + progress.value = 0.4f + + assertTransition( + step = currentStep!!, + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.RUNNING, + progress = 0.4f, + ) + + sceneTransitions.value = + ObservableTransitionState.Transition( + Scenes.Gone, + Scenes.Shade, + flowOf(Scenes.Lockscreen), + progress, + false, + flowOf(false) + ) + + assertTransition( + step = allSteps[allSteps.size - 3], + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.CANCELED, + progress = 0.4f, + ) + + assertTransition( + step = allSteps[allSteps.size - 2], + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.STARTED, + progress = 0.6f, + ) + + assertTransition( + step = allSteps[allSteps.size - 1], + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.FINISHED, + progress = 1f, + ) + } + + /** + * STL: Gone -> Ls, then interrupt in KTF LS -> AOD, then stl still finishes in Ls. After a KTF + * transition is started (UNDEFINED -> LOCKSCREEN) KTF immediately considers the active scene to + * be LOCKSCREEN. This means that all listeners for LOCKSCREEN are active and may start a new + * transition LOCKSCREEN -> *. Here we test LS -> AOD. + * + * KTF is allowed to already start and play the other transition, while the STL transition may + * finish later (gesture completes much later). When we eventually settle the STL transition in + * Ls we do not want to force KTF back to its original destination (LOCKSCREEN). Instead, for + * this scenario the settle can be ignored. + */ + @Test + fun transition_to_ls_scene_interrupted_by_ktf_transition_then_finish_in_lockscreen() = + testScope.runTest { + sceneTransitions.value = goneToLs + + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + assertTransition( + step = currentStep!!, + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.RUNNING, + progress = 0f, + ) + + progress.value = 0.4f + assertTransition( + step = currentStep!!, + state = TransitionState.RUNNING, + progress = 0.4f, + ) + + kosmos.realKeyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = this.javaClass.simpleName, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + animator = null, + modeOnCanceled = TransitionModeOnCanceled.RESET + ) + ) + + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + state = TransitionState.STARTED, + progress = 0f, + ) + + // Scene progress should not affect KTF transition anymore + progress.value = 0.7f + assertTransition(currentStep!!, progress = 0f) + + // Scene transition still finishes but should not impact KTF transition + sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen) + + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + state = TransitionState.STARTED, + progress = 0f, + ) + } + + /** + * STL: Gone -> Ls, then interrupt in KTF LS -> AOD, then stl finishes in Gone. + * + * Refers to: `transition_to_ls_scene_interrupted_by_ktf_transition_then_finish_in_lockscreen` + * + * This is similar to the previous scenario but the gesture may have gone back to its origin. In + * this case we can not ignore the settlement, because whatever KTF has done in the meantime it + * needs to immediately finish in UNDEFINED (there is a jump cut). + */ + @Test + fun transition_to_ls_scene_interrupted_by_ktf_transition_then_finish_in_gone() = + testScope.runTest { + sceneTransitions.value = goneToLs + + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + assertTransition( + step = currentStep!!, + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.RUNNING, + progress = 0f, + ) + + progress.value = 0.4f + assertTransition( + step = currentStep!!, + state = TransitionState.RUNNING, + progress = 0.4f, + ) + + kosmos.realKeyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = this.javaClass.simpleName, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + animator = null, + modeOnCanceled = TransitionModeOnCanceled.RESET + ) + ) + + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + state = TransitionState.STARTED, + progress = 0f, + ) + + progress.value = 0.7f + assertThat(currentStep?.value).isEqualTo(0f) + + sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone) + + assertTransition( + step = currentStep!!, + from = KeyguardState.AOD, + to = KeyguardState.UNDEFINED, + state = TransitionState.FINISHED, + progress = 1f, + ) + } + + /** + * STL: Gone -> Ls, then interrupt in KTF LS -> AOD, then STL Gone -> Shade + * + * Refers to: `transition_to_ls_scene_interrupted_by_ktf_transition_then_finish_in_lockscreen` + * + * This is similar to the previous scenario but the gesture may have been interrupted by any + * other transition. KTF needs to immediately finish in UNDEFINED (there is a jump cut). + */ + @Test + fun transition_to_ls_interrupted_by_ktf_transition_then_interrupted_by_other_transition() = + testScope.runTest { + sceneTransitions.value = goneToLs + + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + assertTransition( + step = currentStep!!, + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.RUNNING, + progress = 0f, + ) + + progress.value = 0.4f + assertTransition( + step = currentStep!!, + state = TransitionState.RUNNING, + progress = 0.4f, + ) + + kosmos.realKeyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = this.javaClass.simpleName, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + animator = null, + modeOnCanceled = TransitionModeOnCanceled.RESET + ) + ) + + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + state = TransitionState.STARTED, + progress = 0f, + ) + + progress.value = 0.7f + assertTransition(currentStep!!, progress = 0f) + + sceneTransitions.value = + ObservableTransitionState.Transition( + Scenes.Gone, + Scenes.Shade, + flowOf(Scenes.Lockscreen), + progress, + false, + flowOf(false) + ) + + assertTransition( + step = currentStep!!, + from = KeyguardState.AOD, + to = KeyguardState.UNDEFINED, + state = TransitionState.FINISHED, + progress = 1f, + ) + } + + /** + * STL: Gone -> Ls, then interrupt in KTF LS -> AOD, then STL Ls -> Shade + * + * In this scenario it is important that the last STL transition Ls -> Shade triggers a cancel + * of the * -> AOD transition but then also properly starts a transition AOD (not LOCKSCREEN) -> + * UNDEFINED transition. + */ + @Test + fun transition_to_ls_interrupted_by_ktf_transition_then_interrupted_by_from_ls_transition() = + testScope.runTest { + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions) + sceneTransitions.value = goneToLs + progress.value = 0.4f + + assertTransition( + step = currentStep!!, + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.RUNNING, + progress = 0.4f, + ) + + kosmos.realKeyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = this.javaClass.simpleName, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + animator = null, + modeOnCanceled = TransitionModeOnCanceled.RESET + ) + ) + + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + state = TransitionState.STARTED, + progress = 0f, + ) + + progress.value = 0.7f + assertTransition(currentStep!!, progress = 0f) + + sceneTransitions.value = + ObservableTransitionState.Transition( + Scenes.Lockscreen, + Scenes.Shade, + flowOf(Scenes.Lockscreen), + progress, + false, + flowOf(false) + ) + allSteps[allSteps.size - 3] + + assertTransition( + step = allSteps[allSteps.size - 3], + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + state = TransitionState.CANCELED, + progress = 0f, + ) + + assertTransition( + step = allSteps[allSteps.size - 2], + from = KeyguardState.AOD, + to = KeyguardState.UNDEFINED, + state = TransitionState.STARTED, + progress = 0f, + ) + + progress.value = 0.2f + assertTransition( + step = allSteps[allSteps.size - 1], + from = KeyguardState.AOD, + to = KeyguardState.UNDEFINED, + state = TransitionState.RUNNING, + progress = 0.2f, + ) + } + + /** + * STL: Gone -> Ls, then interrupt in KTF LS -> AOD, then STL Shade -> Ls + * + * In this scenario it is important KTF is brought back into a FINISHED UNDEFINED state + * considering the state is already on AOD from where a new UNDEFINED -> LOCKSCREEN transition + * can be started. + */ + @Test + fun transition_to_ls_interrupted_by_ktf_transition_then_interrupted_by_to_ls_transition() = + testScope.runTest { + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions) + sceneTransitions.value = goneToLs + progress.value = 0.4f + + assertTransition( + step = currentStep!!, + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.RUNNING, + progress = 0.4f, + ) + + kosmos.realKeyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = this.javaClass.simpleName, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + animator = null, + modeOnCanceled = TransitionModeOnCanceled.RESET + ) + ) + + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + state = TransitionState.STARTED, + progress = 0f, + ) + + progress.value = 0.7f + assertTransition(currentStep!!, progress = 0f) + + sceneTransitions.value = + ObservableTransitionState.Transition( + Scenes.Shade, + Scenes.Lockscreen, + flowOf(Scenes.Lockscreen), + progress, + false, + flowOf(false) + ) + + assertTransition( + step = allSteps[allSteps.size - 5], + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + state = TransitionState.CANCELED, + progress = 0f, + ) + + assertTransition( + step = allSteps[allSteps.size - 4], + from = KeyguardState.AOD, + to = KeyguardState.UNDEFINED, + state = TransitionState.STARTED, + progress = 1f, + ) + + assertTransition( + step = allSteps[allSteps.size - 3], + from = KeyguardState.AOD, + to = KeyguardState.UNDEFINED, + state = TransitionState.FINISHED, + progress = 1f, + ) + + assertTransition( + step = allSteps[allSteps.size - 2], + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.STARTED, + progress = 0f, + ) + + assertTransition( + step = allSteps[allSteps.size - 1], + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.RUNNING, + progress = 0.7f, + ) + } + + /** + * STL: Gone -> Ls, then interrupt multiple canceled KTF transitions, then STL Ls -> Shade + * + * Similar to + * `transition_to_ls_scene_interrupted_by_ktf_transition_then_interrupted_by_from_ls_transition` + * but here KTF is canceled multiple times such that in the end OCCLUDED -> UNDEFINED is + * properly started. (not from AOD or LOCKSCREEN) + * + * Note: there is no test which tests multiple cancels from the STL side, this is because all + * STL transitions trigger a response from LockscreenSceneTransitionInteractor which forces KTF + * into a specific state, so testing each pair is enough. Meanwhile KTF can move around without + * any reaction from LockscreenSceneTransitionInteractor. + */ + @Test + fun transition_to_ls_interrupted_by_ktf_cancel_sequence_interrupted_by_from_ls_transition() = + testScope.runTest { + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions) + sceneTransitions.value = lsToGone + progress.value = 0.4f + + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.RUNNING, + progress = 0.4f, + ) + + kosmos.realKeyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = this.javaClass.simpleName, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + animator = null, + modeOnCanceled = TransitionModeOnCanceled.RESET + ) + ) + + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + state = TransitionState.STARTED, + progress = 0f, + ) + + kosmos.realKeyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = this.javaClass.simpleName, + from = KeyguardState.AOD, + to = KeyguardState.DOZING, + animator = null, + modeOnCanceled = TransitionModeOnCanceled.RESET + ) + ) + + assertTransition( + step = currentStep!!, + from = KeyguardState.AOD, + to = KeyguardState.DOZING, + state = TransitionState.STARTED, + progress = 0f, + ) + + kosmos.realKeyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = this.javaClass.simpleName, + from = KeyguardState.DOZING, + to = KeyguardState.OCCLUDED, + animator = null, + modeOnCanceled = TransitionModeOnCanceled.RESET + ) + ) + + assertTransition( + step = currentStep!!, + from = KeyguardState.DOZING, + to = KeyguardState.OCCLUDED, + state = TransitionState.STARTED, + progress = 0f, + ) + + progress.value = 0.7f + assertTransition(currentStep!!, progress = 0f) + + sceneTransitions.value = + ObservableTransitionState.Transition( + Scenes.Lockscreen, + Scenes.Shade, + flowOf(Scenes.Lockscreen), + progress, + false, + flowOf(false) + ) + + assertTransition( + step = allSteps[allSteps.size - 3], + from = KeyguardState.DOZING, + to = KeyguardState.OCCLUDED, + state = TransitionState.CANCELED, + progress = 0f, + ) + + assertTransition( + step = allSteps[allSteps.size - 2], + from = KeyguardState.OCCLUDED, + to = KeyguardState.UNDEFINED, + state = TransitionState.STARTED, + progress = 0f, + ) + + progress.value = 0.2f + assertTransition( + step = allSteps[allSteps.size - 1], + from = KeyguardState.OCCLUDED, + to = KeyguardState.UNDEFINED, + state = TransitionState.RUNNING, + progress = 0.2f, + ) + } + + /** + * STL: Gone -> Ls, then interrupted by KTF LS -> AOD which is FINISHED before STL Ls -> Shade + * + * Similar to + * `transition_to_ls_scene_interrupted_by_ktf_transition_then_interrupted_by_from_ls_transition` + * but here KTF is finishing the transition and only then gets interrupted. Should correctly + * start AOD -> UNDEFINED. + */ + @Test + fun transition_to_ls_scene_interrupted_and_finished_by_ktf_interrupted_by_from_ls_transition() = + testScope.runTest { + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions) + sceneTransitions.value = lsToGone + progress.value = 0.4f + + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.RUNNING, + progress = 0.4f, + ) + + val ktfUuid = + kosmos.realKeyguardTransitionRepository.startTransition( + TransitionInfo( + ownerName = this.javaClass.simpleName, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + animator = null, + modeOnCanceled = TransitionModeOnCanceled.RESET + ) + ) + + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + state = TransitionState.STARTED, + progress = 0f, + ) + + kosmos.realKeyguardTransitionRepository.updateTransition( + ktfUuid!!, + 1f, + TransitionState.FINISHED + ) + + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + state = TransitionState.FINISHED, + progress = 1f, + ) + + sceneTransitions.value = + ObservableTransitionState.Transition( + Scenes.Lockscreen, + Scenes.Shade, + flowOf(Scenes.Lockscreen), + progress, + false, + flowOf(false) + ) + + assertTransition( + step = allSteps[allSteps.size - 3], + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + state = TransitionState.FINISHED, + progress = 1f, + ) + + assertTransition( + step = allSteps[allSteps.size - 2], + from = KeyguardState.AOD, + to = KeyguardState.UNDEFINED, + state = TransitionState.STARTED, + progress = 0f, + ) + + progress.value = 0.2f + assertTransition( + step = allSteps[allSteps.size - 1], + from = KeyguardState.AOD, + to = KeyguardState.UNDEFINED, + state = TransitionState.RUNNING, + progress = 0.2f, + ) + } + + /** + * STL: Ls -> Gone, then interrupted by Ls -> Bouncer. This happens when the next transition is + * immediately started from Gone without settling in Idle. This specifically happens when + * dragging down on Ls and then changing direction. The transition will switch from -> Shade to + * -> Bouncer without settling or signaling any cancellation as STL considers this to be the + * same gesture. + * + * In STL there is no guarantee that transitions settle in Idle before continuing. + */ + @Ignore("Suffers from a race condition that will be fixed in followup CL") + @Test + fun transition_from_ls_scene_interrupted_by_other_from_ls_transition() = + testScope.runTest { + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions) + sceneTransitions.value = lsToGone + + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.RUNNING, + progress = 0f, + ) + + progress.value = 0.4f + sceneTransitions.value = + ObservableTransitionState.Transition( + Scenes.Lockscreen, + Scenes.Bouncer, + flowOf(Scenes.Lockscreen), + progress, + false, + flowOf(false) + ) + + assertTransition( + step = allSteps[allSteps.size - 5], + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.CANCELED, + progress = 0.4f, + ) + + assertTransition( + step = allSteps[allSteps.size - 4], + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.STARTED, + progress = 0.6f, + ) + + assertTransition( + step = allSteps[allSteps.size - 3], + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.FINISHED, + progress = 1f, + ) + + assertTransition( + step = allSteps[allSteps.size - 2], + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.STARTED, + progress = 0f, + ) + } + + /** + * STL: Ls -> Gone, then interrupted by Gone -> Ls. This happens when the next transition is + * immediately started from Gone without settling in Idle. In STL there is no guarantee that + * transitions settle in Idle before continuing. + */ + @Test + fun transition_from_ls_scene_interrupted_by_to_ls_transition() = + testScope.runTest { + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + val allSteps by collectValues(kosmos.realKeyguardTransitionRepository.transitions) + sceneTransitions.value = lsToGone + progress.value = 0.4f + + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.RUNNING, + progress = 0.4f, + ) + + sceneTransitions.value = + ObservableTransitionState.Transition( + Scenes.Gone, + Scenes.Lockscreen, + flowOf(Scenes.Lockscreen), + progress, + false, + flowOf(false) + ) + + assertTransition( + step = allSteps[allSteps.size - 3], + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.FINISHED, + progress = 1f, + ) + + assertTransition( + step = allSteps[allSteps.size - 2], + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.STARTED, + progress = 0f, + ) + + progress.value = 0.2f + assertTransition( + step = allSteps[allSteps.size - 1], + from = KeyguardState.UNDEFINED, + to = KeyguardState.LOCKSCREEN, + state = TransitionState.RUNNING, + progress = 0.2f, + ) + } + + /** + * STL: Ls -> Gone, then interrupted by Gone -> Bouncer. This happens when the next transition + * is immediately started from Gone without settling in Idle. In STL there is no guarantee that + * transitions settle in Idle before continuing. + */ + @Test + fun transition_from_ls_scene_interrupted_by_other_stl_transition() = + testScope.runTest { + val currentStep by collectLastValue(kosmos.realKeyguardTransitionRepository.transitions) + sceneTransitions.value = lsToGone + progress.value = 0.4f + + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.RUNNING, + progress = 0.4f, + ) + + sceneTransitions.value = + ObservableTransitionState.Transition( + Scenes.Gone, + Scenes.Bouncer, + flowOf(Scenes.Lockscreen), + progress, + false, + flowOf(false) + ) + + assertTransition( + step = currentStep!!, + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + state = TransitionState.FINISHED, + progress = 1f, + ) + } + + private fun assertTransition( + step: TransitionStep, + from: KeyguardState? = null, + to: KeyguardState? = null, + state: TransitionState? = null, + progress: Float? = null + ) { + if (from != null) assertThat(step.from).isEqualTo(from) + if (to != null) assertThat(step.to).isEqualTo(to) + if (state != null) assertThat(step.transitionState).isEqualTo(state) + if (progress != null) assertThat(step.value).isEqualTo(progress) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt index 2f650c4420a0..040d3b8f09cb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt @@ -85,7 +85,6 @@ class KeyguardClockViewBinderTest : SysuiTestCase() { setupWeatherClock() KeyguardClockViewBinder.updateBurnInLayer(rootView, clockViewModel, ClockSize.LARGE) verify(burnInLayer).removeView(smallClockView) - verify(burnInLayer).addView(largeClockView) } @Test @@ -101,7 +100,6 @@ class KeyguardClockViewBinderTest : SysuiTestCase() { setupNonWeatherClock() KeyguardClockViewBinder.updateBurnInLayer(rootView, clockViewModel, ClockSize.SMALL) verify(burnInLayer).addView(smallClockView) - verify(burnInLayer).removeView(largeClockView) } private fun setupWeatherClock() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt index ba2efe6d7443..b3cc5c949cb8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor import com.android.systemui.keyguard.domain.interactor.keyguardSmartspaceInteractor import com.android.systemui.keyguard.shared.model.ClockSize 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.kosmos.Kosmos import com.android.systemui.kosmos.testScope @@ -117,6 +118,7 @@ class ClockSectionTest : SysuiTestCase() { context, keyguardSmartspaceViewModel, { keyguardBlueprintInteractor }, + keyguardRootViewModel, ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt index 0bca36775e9f..f61ddeb47514 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.accessibility.data.repository.fakeAccessibilityRepository import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository import com.android.systemui.coroutines.collectLastValue @@ -150,6 +151,51 @@ class DeviceEntryIconViewModelTest : SysuiTestCase() { assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.NONE) } + fun accessibilityDelegateHint_accessibilityNotEnabled() = + testScope.runTest { + val accessibilityDelegateHint by collectLastValue(underTest.accessibilityDelegateHint) + kosmos.fakeAccessibilityRepository.isEnabled.value = false + assertThat(accessibilityDelegateHint) + .isEqualTo(DeviceEntryIconView.AccessibilityHintType.NONE) + } + + @Test + fun accessibilityDelegateHint_accessibilityEnabled_locked() = + testScope.runTest { + val accessibilityDelegateHint by collectLastValue(underTest.accessibilityDelegateHint) + kosmos.fakeAccessibilityRepository.isEnabled.value = true + + // interactive lock icon + keyguardRepository.setKeyguardDismissible(false) + fingerprintPropertyRepository.supportsUdfps() + + assertThat(accessibilityDelegateHint) + .isEqualTo(DeviceEntryIconView.AccessibilityHintType.AUTHENTICATE) + + // non-interactive lock icon + keyguardRepository.setKeyguardDismissible(false) + fingerprintPropertyRepository.supportsRearFps() + + assertThat(accessibilityDelegateHint) + .isEqualTo(DeviceEntryIconView.AccessibilityHintType.NONE) + } + + @Test + fun accessibilityDelegateHint_accessibilityEnabled_unlocked() = + testScope.runTest { + val accessibilityDelegateHint by collectLastValue(underTest.accessibilityDelegateHint) + kosmos.fakeAccessibilityRepository.isEnabled.value = true + + // interactive unlock icon + keyguardRepository.setKeyguardDismissible(true) + fingerprintPropertyRepository.supportsUdfps() + advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay + runCurrent() + + assertThat(accessibilityDelegateHint) + .isEqualTo(DeviceEntryIconView.AccessibilityHintType.ENTER) + } + private fun deviceEntryIconTransitionAlpha(alpha: Float) { deviceEntryIconTransition.setDeviceEntryParentViewAlpha(alpha) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 1881a9e0c205..16421a0f83b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -88,7 +88,7 @@ import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { +class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { @Mock private lateinit var expandable: Expandable @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper @@ -115,7 +115,7 @@ class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization?) : SysuiTest private val kosmos = testKosmos() init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt index 0c98cff89ee2..768d4468de4f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt @@ -53,7 +53,7 @@ import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -class KeyguardClockViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { +class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { val kosmos = testKosmos() val testScope = kosmos.testScope val underTest = kosmos.keyguardClockViewModel @@ -67,7 +67,7 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization?) : SysuiTestCase( var faceConfig = ClockFaceConfig() init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt index e56a25345436..265ade3fac0f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt @@ -172,20 +172,20 @@ class LegacyMediaDataFilterImplTest : SysuiTestCase() { fun testOnRemovedForCurrent_callsListener() { // GIVEN a media was removed for main user mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) - mediaDataFilter.onMediaDataRemoved(KEY) + mediaDataFilter.onMediaDataRemoved(KEY, false) // THEN we should tell the listener - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) } @Test fun testOnRemovedForGuest_doesNotCallListener() { // GIVEN a media was removed for guest user mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest) - mediaDataFilter.onMediaDataRemoved(KEY) + mediaDataFilter.onMediaDataRemoved(KEY, false) // THEN we should NOT tell the listener - verify(listener, never()).onMediaDataRemoved(eq(KEY)) + verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean()) } @Test @@ -197,7 +197,7 @@ class LegacyMediaDataFilterImplTest : SysuiTestCase() { setUser(USER_GUEST) // THEN we should remove the main user's media - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) } @Test @@ -230,7 +230,7 @@ class LegacyMediaDataFilterImplTest : SysuiTestCase() { setPrivateProfileUnavailable() // THEN we should add the private profile media - verify(listener).onMediaDataRemoved(eq(KEY_ALT)) + verify(listener).onMediaDataRemoved(eq(KEY_ALT), eq(false)) } @Test @@ -360,7 +360,7 @@ class LegacyMediaDataFilterImplTest : SysuiTestCase() { @Test fun testOnNotificationRemoved_doesntHaveMedia() { mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain) - mediaDataFilter.onMediaDataRemoved(KEY) + mediaDataFilter.onMediaDataRemoved(KEY, false) assertThat(mediaDataFilter.hasAnyMediaOrRecommendation()).isFalse() assertThat(mediaDataFilter.hasAnyMedia()).isFalse() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt index 5a2d22d0d503..99bf2db2ff98 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt @@ -346,7 +346,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { // THEN it is removed and listeners are informed foregroundExecutor.advanceClockToLast() foregroundExecutor.runAllReady() - verify(listener).onMediaDataRemoved(PACKAGE_NAME) + verify(listener).onMediaDataRemoved(PACKAGE_NAME, false) } @Test @@ -532,7 +532,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { addNotificationAndLoad() val data = mediaDataCaptor.value mediaDataManager.onNotificationRemoved(KEY) - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) } @@ -777,7 +777,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { eq(false) ) assertThat(mediaDataCaptor.value.resumption).isTrue() - verify(listener, never()).onMediaDataRemoved(eq(KEY)) + verify(listener, never()).onMediaDataRemoved(eq(KEY), eq(false)) // WHEN the second is removed mediaDataManager.onNotificationRemoved(KEY_2) // THEN the data is for resumption and the second key is removed @@ -791,7 +791,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { eq(false) ) assertThat(mediaDataCaptor.value.resumption).isTrue() - verify(listener).onMediaDataRemoved(eq(KEY_2)) + verify(listener).onMediaDataRemoved(eq(KEY_2), eq(false)) } @Test @@ -816,7 +816,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { mediaDataManager.onNotificationRemoved(KEY) // THEN the media data is removed - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) } @Test @@ -866,7 +866,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { mediaDataManager.onNotificationRemoved(KEY) // THEN the media data is removed - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) } @Test @@ -905,7 +905,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { assertThat(mediaDataCaptor.value.isPlaying).isFalse() // And the oldest resume control was removed - verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME")) + verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME"), eq(false)) } fun testOnNotificationRemoved_lockDownMode() { @@ -915,7 +915,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { val data = mediaDataCaptor.value mediaDataManager.onNotificationRemoved(KEY) - verify(listener, never()).onMediaDataRemoved(eq(KEY)) + verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean()) verify(logger, never()) .logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) @@ -1148,7 +1148,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { mediaDataManager.setMediaResumptionEnabled(false) // THEN the resume controls are dismissed - verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME)) + verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) } @@ -1156,19 +1156,19 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { fun testDismissMedia_listenerCalled() { addNotificationAndLoad() val data = mediaDataCaptor.value - val removed = mediaDataManager.dismissMediaData(KEY, 0L) + val removed = mediaDataManager.dismissMediaData(KEY, 0L, true) assertThat(removed).isTrue() foregroundExecutor.advanceClockToLast() foregroundExecutor.runAllReady() - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(true)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) } @Test fun testDismissMedia_keyDoesNotExist_returnsFalse() { - val removed = mediaDataManager.dismissMediaData(KEY, 0L) + val removed = mediaDataManager.dismissMediaData(KEY, 0L, true) assertThat(removed).isFalse() } @@ -2077,7 +2077,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { sessionCallbackCaptor.value.invoke(KEY) // It remains as a regular player - verify(listener, never()).onMediaDataRemoved(eq(KEY)) + verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean()) verify(listener, never()) .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) } @@ -2093,7 +2093,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { mediaDataManager.onNotificationRemoved(KEY) // It is fully removed - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) verify(listener, never()) .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) @@ -2146,7 +2146,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { mediaDataManager.onNotificationRemoved(KEY) // It remains as a regular player - verify(listener, never()).onMediaDataRemoved(eq(KEY)) + verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean()) verify(listener, never()) .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) } @@ -2199,7 +2199,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { sessionCallbackCaptor.value.invoke(KEY) // It is fully removed - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) verify(listener, never()) .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) @@ -2253,7 +2253,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { sessionCallbackCaptor.value.invoke(KEY) // It is fully removed. - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) verify(listener, never()) .onMediaDataLoaded( @@ -2279,7 +2279,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { sessionCallbackCaptor.value.invoke(KEY) // It is fully removed - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) verify(listener, never()) .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) @@ -2329,7 +2329,7 @@ class LegacyMediaDataManagerImplTest : SysuiTestCase() { mediaDataManager.onNotificationRemoved(KEY) // We still make sure to remove it - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java index bb5b57287a9a..dd05a0d12429 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java @@ -202,24 +202,24 @@ public class MediaDataCombineLatestTest extends SysuiTestCase { @Test public void mediaDataRemoved() { // WHEN media data is removed without first receiving device or data - mManager.onMediaDataRemoved(KEY); + mManager.onMediaDataRemoved(KEY, false); // THEN a removed event isn't emitted - verify(mListener, never()).onMediaDataRemoved(eq(KEY)); + verify(mListener, never()).onMediaDataRemoved(eq(KEY), anyBoolean()); } @Test public void mediaDataRemovedAfterMediaEvent() { mManager.onMediaDataLoaded(KEY, null, mMediaData, true /* immediately */, 0 /* receivedSmartspaceCardLatency */, false /* isSsReactivated */); - mManager.onMediaDataRemoved(KEY); - verify(mListener).onMediaDataRemoved(eq(KEY)); + mManager.onMediaDataRemoved(KEY, false); + verify(mListener).onMediaDataRemoved(eq(KEY), eq(false)); } @Test public void mediaDataRemovedAfterDeviceEvent() { mManager.onMediaDeviceChanged(KEY, null, mDeviceData); - mManager.onMediaDataRemoved(KEY); - verify(mListener).onMediaDataRemoved(eq(KEY)); + mManager.onMediaDataRemoved(KEY, false); + verify(mListener).onMediaDataRemoved(eq(KEY), eq(false)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt index 857af66cab16..35eefd91bc8e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt @@ -168,7 +168,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { @Test fun onDataLoadedForCurrentUser_updatesLoadedStates() = testScope.runTest { - val sortedMedia by collectLastValue(repository.sortedMedia) + val currentMedia by collectLastValue(repository.currentMedia) val mediaCommonModel = MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(dataMain.instanceId)) @@ -176,13 +176,13 @@ class MediaDataFilterImplTest : SysuiTestCase() { verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain), eq(true), eq(0), eq(false)) - assertThat(sortedMedia?.values).containsExactly(mediaCommonModel) + assertThat(currentMedia).containsExactly(mediaCommonModel) } @Test fun onDataLoadedForGuest_doesNotUpdateLoadedStates() = testScope.runTest { - val sortedMedia by collectLastValue(repository.sortedMedia) + val currentMedia by collectLastValue(repository.currentMedia) val mediaCommonModel = MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(dataMain.instanceId)) @@ -190,64 +190,63 @@ class MediaDataFilterImplTest : SysuiTestCase() { verify(listener, never()) .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean()) - assertThat(sortedMedia?.values).doesNotContain(mediaCommonModel) + assertThat(currentMedia).doesNotContain(mediaCommonModel) } @Test fun onRemovedForCurrent_updatesLoadedStates() = testScope.runTest { - val sortedMedia by collectLastValue(repository.sortedMedia) + val currentMedia by collectLastValue(repository.currentMedia) val mediaCommonModel = MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(dataMain.instanceId)) // GIVEN a media was removed for main user mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) - assertThat(sortedMedia?.values).containsExactly(mediaCommonModel) + assertThat(currentMedia).containsExactly(mediaCommonModel) - mediaDataFilter.onMediaDataRemoved(KEY) + mediaDataFilter.onMediaDataRemoved(KEY, false) - verify(listener).onMediaDataRemoved(eq(KEY)) - assertThat(sortedMedia?.values).doesNotContain(mediaCommonModel) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) + assertThat(currentMedia).doesNotContain(mediaCommonModel) } @Test fun onRemovedForGuest_doesNotUpdateLoadedStates() = testScope.runTest { - val sortedMedia by collectLastValue(repository.sortedMedia) + val currentMedia by collectLastValue(repository.currentMedia) // GIVEN a media was removed for guest user mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest) - mediaDataFilter.onMediaDataRemoved(KEY) + mediaDataFilter.onMediaDataRemoved(KEY, false) - verify(listener, never()).onMediaDataRemoved(eq(KEY)) - assertThat(sortedMedia).isEmpty() + verify(listener, never()).onMediaDataRemoved(eq(KEY), eq(false)) + assertThat(currentMedia).isEmpty() } @Test fun onUserSwitched_removesOldUserControls() = testScope.runTest { - val sortedMedia by collectLastValue(repository.sortedMedia) + val currentMedia by collectLastValue(repository.currentMedia) val mediaLoaded = MediaDataLoadingModel.Loaded(dataMain.instanceId) // GIVEN that we have a media loaded for main user mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) - assertThat(sortedMedia?.values) - .containsExactly(MediaCommonModel.MediaControl(mediaLoaded)) + assertThat(currentMedia).containsExactly(MediaCommonModel.MediaControl(mediaLoaded)) // and we switch to guest user setUser(USER_GUEST) // THEN we should remove the main user's media - verify(listener).onMediaDataRemoved(eq(KEY)) - assertThat(sortedMedia).isEmpty() + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) + assertThat(currentMedia).isEmpty() } @Test fun onUserSwitched_addsNewUserControls() = testScope.runTest { - val sortedMedia by collectLastValue(repository.sortedMedia) + val currentMedia by collectLastValue(repository.currentMedia) val guestLoadedStatesModel = MediaDataLoadingModel.Loaded(dataGuest.instanceId) val mainLoadedStatesModel = MediaDataLoadingModel.Loaded(dataMain.instanceId) @@ -272,16 +271,16 @@ class MediaDataFilterImplTest : SysuiTestCase() { anyInt(), anyBoolean() ) - assertThat(sortedMedia?.values) + assertThat(currentMedia) .containsExactly(MediaCommonModel.MediaControl(guestLoadedStatesModel)) - assertThat(sortedMedia?.values) + assertThat(currentMedia) .doesNotContain(MediaCommonModel.MediaControl(mainLoadedStatesModel)) } @Test fun onProfileChanged_profileUnavailable_updateStates() = testScope.runTest { - val sortedMedia by collectLastValue(repository.sortedMedia) + val currentMedia by collectLastValue(repository.currentMedia) // GIVEN that we had some media for both profiles mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain) @@ -292,8 +291,8 @@ class MediaDataFilterImplTest : SysuiTestCase() { val mediaLoadedStatesModel = MediaDataLoadingModel.Loaded(dataMain.instanceId) // THEN we should remove the private profile media - verify(listener).onMediaDataRemoved(eq(KEY_ALT)) - assertThat(sortedMedia?.values) + verify(listener).onMediaDataRemoved(eq(KEY_ALT), eq(false)) + assertThat(currentMedia) .containsExactly(MediaCommonModel.MediaControl(mediaLoadedStatesModel)) } @@ -503,7 +502,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain) - mediaDataFilter.onMediaDataRemoved(KEY) + mediaDataFilter.onMediaDataRemoved(KEY, false) assertThat(hasAnyMediaOrRecommendation(selectedUserEntries, smartspaceMediaData)) .isFalse() assertThat(hasAnyMedia(selectedUserEntries)).isFalse() @@ -523,13 +522,13 @@ class MediaDataFilterImplTest : SysuiTestCase() { val selectedUserEntries by collectLastValue(repository.selectedUserEntries) val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) val reactivatedKey by collectLastValue(repository.reactivatedId) - val sortedMedia by collectLastValue(repository.sortedMedia) + val currentMedia by collectLastValue(repository.currentMedia) val recommendationsLoadingModel = SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY, isPrioritized = true) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - assertThat(sortedMedia?.values) + assertThat(currentMedia) .containsExactly(MediaCommonModel.MediaRecommendations(recommendationsLoadingModel)) assertThat( hasActiveMediaOrRecommendation( @@ -552,13 +551,13 @@ class MediaDataFilterImplTest : SysuiTestCase() { val selectedUserEntries by collectLastValue(repository.selectedUserEntries) val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) val reactivatedKey by collectLastValue(repository.reactivatedId) - val sortedMedia by collectLastValue(repository.sortedMedia) + val currentMedia by collectLastValue(repository.currentMedia) whenever(smartspaceData.isActive).thenReturn(false) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - assertThat(sortedMedia).isEmpty() + assertThat(currentMedia).isEmpty() assertThat( hasActiveMediaOrRecommendation( selectedUserEntries, @@ -581,7 +580,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { val selectedUserEntries by collectLastValue(repository.selectedUserEntries) val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) val reactivatedKey by collectLastValue(repository.reactivatedId) - val sortedMedia by collectLastValue(repository.sortedMedia) + val currentMedia by collectLastValue(repository.currentMedia) val recsCommonModel = MediaCommonModel.MediaRecommendations( SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY, isPrioritized = true) @@ -596,7 +595,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { clock.advanceTime(MediaDataFilterImpl.SMARTSPACE_MAX_AGE + 100) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - assertThat(sortedMedia?.values).containsExactly(recsCommonModel, controlCommonModel) + assertThat(currentMedia).containsExactly(recsCommonModel, controlCommonModel) assertThat( hasActiveMediaOrRecommendation( selectedUserEntries, @@ -618,7 +617,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { val selectedUserEntries by collectLastValue(repository.selectedUserEntries) val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) val reactivatedKey by collectLastValue(repository.reactivatedId) - val sortedMedia by collectLastValue(repository.sortedMedia) + val currentMedia by collectLastValue(repository.currentMedia) whenever(smartspaceData.isActive).thenReturn(false) val dataOld = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) @@ -626,7 +625,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { clock.advanceTime(MediaDataFilterImpl.SMARTSPACE_MAX_AGE + 100) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) - assertThat(sortedMedia?.values) + assertThat(currentMedia) .doesNotContain( MediaCommonModel.MediaRecommendations( SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY) @@ -652,7 +651,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { val selectedUserEntries by collectLastValue(repository.selectedUserEntries) val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) val reactivatedKey by collectLastValue(repository.reactivatedId) - val sortedMedia by collectLastValue(repository.sortedMedia) + val currentMedia by collectLastValue(repository.currentMedia) whenever(smartspaceData.isActive).thenReturn(false) @@ -665,7 +664,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) - assertThat(sortedMedia?.values).containsExactly(controlCommonModel) + assertThat(currentMedia).containsExactly(controlCommonModel) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) @@ -673,7 +672,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) // THEN we should treat the media as not active instead - assertThat(sortedMedia?.values).containsExactly(controlCommonModel) + assertThat(currentMedia).containsExactly(controlCommonModel) assertThat( hasActiveMediaOrRecommendation( selectedUserEntries, @@ -696,7 +695,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { val selectedUserEntries by collectLastValue(repository.selectedUserEntries) val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) val reactivatedKey by collectLastValue(repository.reactivatedId) - val sortedMedia by collectLastValue(repository.sortedMedia) + val currentMedia by collectLastValue(repository.currentMedia) whenever(smartspaceData.isValid()).thenReturn(false) // WHEN we have media that was recently played, but not currently active @@ -707,7 +706,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { true ) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) - assertThat(sortedMedia?.values).containsExactly(controlCommonModel) + assertThat(currentMedia).containsExactly(controlCommonModel) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) @@ -717,7 +716,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { // THEN we should treat the media as active instead val dataCurrentAndActive = dataCurrent.copy(active = true) - assertThat(sortedMedia?.values).containsExactly(controlCommonModel) + assertThat(currentMedia).containsExactly(controlCommonModel) assertThat( hasActiveMediaOrRecommendation( selectedUserEntries, @@ -747,7 +746,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { val selectedUserEntries by collectLastValue(repository.selectedUserEntries) val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) val reactivatedKey by collectLastValue(repository.reactivatedId) - val sortedMedia by collectLastValue(repository.sortedMedia) + val currentMedia by collectLastValue(repository.currentMedia) // WHEN we have media that was recently played, but not currently active val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) val controlCommonModel = @@ -762,7 +761,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) - assertThat(sortedMedia?.values).containsExactly(controlCommonModel) + assertThat(currentMedia).containsExactly(controlCommonModel) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) @@ -790,7 +789,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { ) .isTrue() // Smartspace update should also be propagated but not prioritized. - assertThat(sortedMedia?.values).containsExactly(controlCommonModel, recsCommonModel) + assertThat(currentMedia).containsExactly(controlCommonModel, recsCommonModel) verify(listener) .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) verify(logger).logRecommendationAdded(SMARTSPACE_PACKAGE, SMARTSPACE_INSTANCE_ID) @@ -803,13 +802,13 @@ class MediaDataFilterImplTest : SysuiTestCase() { val selectedUserEntries by collectLastValue(repository.selectedUserEntries) val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) val reactivatedKey by collectLastValue(repository.reactivatedId) - val sortedMedia by collectLastValue(repository.sortedMedia) + val currentMedia by collectLastValue(repository.currentMedia) mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY) verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY) - assertThat(sortedMedia?.values).isEmpty() + assertThat(currentMedia).isEmpty() assertThat( hasActiveMediaOrRecommendation( selectedUserEntries, @@ -827,7 +826,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { val selectedUserEntries by collectLastValue(repository.selectedUserEntries) val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) val reactivatedKey by collectLastValue(repository.reactivatedId) - val sortedMedia by collectLastValue(repository.sortedMedia) + val currentMedia by collectLastValue(repository.currentMedia) val controlCommonModel = MediaCommonModel.MediaControl( MediaDataLoadingModel.Loaded(dataMain.instanceId), @@ -836,7 +835,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) - assertThat(sortedMedia?.values).containsExactly(controlCommonModel) + assertThat(currentMedia).containsExactly(controlCommonModel) verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) @@ -857,7 +856,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { mediaDataFilter.onSmartspaceMediaDataRemoved(SMARTSPACE_KEY) verify(listener).onSmartspaceMediaDataRemoved(SMARTSPACE_KEY) - assertThat(sortedMedia?.values).containsExactly(controlCommonModel) + assertThat(currentMedia).containsExactly(controlCommonModel) assertThat( hasActiveMediaOrRecommendation( selectedUserEntries, @@ -875,7 +874,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { val selectedUserEntries by collectLastValue(repository.selectedUserEntries) val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) val reactivatedKey by collectLastValue(repository.reactivatedId) - val sortedMedia by collectLastValue(repository.sortedMedia) + val currentMedia by collectLastValue(repository.currentMedia) val recsCommonModel = MediaCommonModel.MediaRecommendations( SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY) @@ -887,7 +886,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { verify(listener) .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) - assertThat(sortedMedia?.values).containsExactly(recsCommonModel) + assertThat(currentMedia).containsExactly(recsCommonModel) assertThat( hasActiveMediaOrRecommendation( selectedUserEntries, @@ -906,7 +905,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { val selectedUserEntries by collectLastValue(repository.selectedUserEntries) val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) val reactivatedKey by collectLastValue(repository.reactivatedId) - val sortedMedia by collectLastValue(repository.sortedMedia) + val currentMedia by collectLastValue(repository.currentMedia) val recsCommonModel = MediaCommonModel.MediaRecommendations( SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY) @@ -926,7 +925,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) - assertThat(sortedMedia?.values).containsExactly(controlCommonModel) + assertThat(currentMedia).containsExactly(controlCommonModel) // And an inactive recommendation is loaded mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) @@ -936,7 +935,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) verify(listener, never()) .onMediaDataLoaded(any(), any(), any(), anyBoolean(), anyInt(), anyBoolean()) - assertThat(sortedMedia?.values).containsExactly(controlCommonModel, recsCommonModel) + assertThat(currentMedia).containsExactly(controlCommonModel, recsCommonModel) assertThat( hasActiveMediaOrRecommendation( selectedUserEntries, @@ -974,7 +973,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { val selectedUserEntries by collectLastValue(repository.selectedUserEntries) val smartspaceMediaData by collectLastValue(repository.smartspaceMediaData) val reactivatedKey by collectLastValue(repository.reactivatedId) - val sortedMedia by collectLastValue(repository.sortedMedia) + val currentMedia by collectLastValue(repository.currentMedia) val recsCommonModel = MediaCommonModel.MediaRecommendations( SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY) @@ -990,7 +989,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) - assertThat(sortedMedia?.values).containsExactly(controlCommonModel) + assertThat(currentMedia).containsExactly(controlCommonModel) // AND we get a smartspace signal with extra to trigger resume runCurrent() @@ -1009,7 +1008,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { eq(100), eq(true) ) - assertThat(sortedMedia?.values).containsExactly(controlCommonModel, recsCommonModel) + assertThat(currentMedia).containsExactly(controlCommonModel, recsCommonModel) assertThat( hasActiveMediaOrRecommendation( selectedUserEntries, @@ -1026,7 +1025,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { @Test fun smartspaceLoaded_notShouldTriggerResume_doesNotTrigger() = testScope.runTest { - val sortedMedia by collectLastValue(repository.sortedMedia) + val currentMedia by collectLastValue(repository.currentMedia) val recsCommonModel = MediaCommonModel.MediaRecommendations( SmartspaceMediaLoadingModel.Loaded(SMARTSPACE_KEY) @@ -1043,7 +1042,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { verify(listener) .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) - assertThat(sortedMedia?.values).containsExactly(controlCommonModel) + assertThat(currentMedia).containsExactly(controlCommonModel) // AND we get a smartspace signal with extra to not trigger resume val extras = Bundle().apply { putBoolean(EXTRA_KEY_TRIGGER_RESUME, false) } @@ -1056,7 +1055,7 @@ class MediaDataFilterImplTest : SysuiTestCase() { // But the smartspace update is still propagated verify(listener) .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) - assertThat(sortedMedia?.values).containsExactly(controlCommonModel, recsCommonModel) + assertThat(currentMedia).containsExactly(controlCommonModel, recsCommonModel) } private fun hasActiveMediaOrRecommendation( diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt index 1de7ee339c3e..5791826ab1f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt @@ -384,7 +384,7 @@ class MediaDataProcessorTest : SysuiTestCase() { // THEN it is removed and listeners are informed foregroundExecutor.advanceClockToLast() foregroundExecutor.runAllReady() - verify(listener).onMediaDataRemoved(PACKAGE_NAME) + verify(listener).onMediaDataRemoved(PACKAGE_NAME, false) } @Test @@ -567,7 +567,7 @@ class MediaDataProcessorTest : SysuiTestCase() { addNotificationAndLoad() val data = mediaDataCaptor.value mediaDataProcessor.onNotificationRemoved(KEY) - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) } @@ -812,7 +812,7 @@ class MediaDataProcessorTest : SysuiTestCase() { eq(false) ) assertThat(mediaDataCaptor.value.resumption).isTrue() - verify(listener, never()).onMediaDataRemoved(eq(KEY)) + verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean()) // WHEN the second is removed mediaDataProcessor.onNotificationRemoved(KEY_2) // THEN the data is for resumption and the second key is removed @@ -826,7 +826,7 @@ class MediaDataProcessorTest : SysuiTestCase() { eq(false) ) assertThat(mediaDataCaptor.value.resumption).isTrue() - verify(listener).onMediaDataRemoved(eq(KEY_2)) + verify(listener).onMediaDataRemoved(eq(KEY_2), eq(false)) } @Test @@ -851,7 +851,7 @@ class MediaDataProcessorTest : SysuiTestCase() { mediaDataProcessor.onNotificationRemoved(KEY) // THEN the media data is removed - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) } @Test @@ -901,7 +901,7 @@ class MediaDataProcessorTest : SysuiTestCase() { mediaDataProcessor.onNotificationRemoved(KEY) // THEN the media data is removed - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) } @Test @@ -940,7 +940,7 @@ class MediaDataProcessorTest : SysuiTestCase() { assertThat(mediaDataCaptor.value.isPlaying).isFalse() // And the oldest resume control was removed - verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME")) + verify(listener).onMediaDataRemoved(eq("0:$PACKAGE_NAME"), eq(false)) } fun testOnNotificationRemoved_lockDownMode() { @@ -950,7 +950,7 @@ class MediaDataProcessorTest : SysuiTestCase() { val data = mediaDataCaptor.value mediaDataProcessor.onNotificationRemoved(KEY) - verify(listener, never()).onMediaDataRemoved(eq(KEY)) + verify(listener, never()).onMediaDataRemoved(eq(KEY), eq(false)) verify(logger, never()) .logActiveConvertedToResume(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) @@ -1183,7 +1183,7 @@ class MediaDataProcessorTest : SysuiTestCase() { mediaDataProcessor.setMediaResumptionEnabled(false) // THEN the resume controls are dismissed - verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME)) + verify(listener).onMediaDataRemoved(eq(PACKAGE_NAME), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) } @@ -1191,19 +1191,19 @@ class MediaDataProcessorTest : SysuiTestCase() { fun testDismissMedia_listenerCalled() { addNotificationAndLoad() val data = mediaDataCaptor.value - val removed = mediaDataProcessor.dismissMediaData(KEY, 0L) + val removed = mediaDataProcessor.dismissMediaData(KEY, 0L, true) assertThat(removed).isTrue() foregroundExecutor.advanceClockToLast() foregroundExecutor.runAllReady() - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(true)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) } @Test fun testDismissMedia_keyDoesNotExist_returnsFalse() { - val removed = mediaDataProcessor.dismissMediaData(KEY, 0L) + val removed = mediaDataProcessor.dismissMediaData(KEY, 0L, true) assertThat(removed).isFalse() } @@ -2102,7 +2102,7 @@ class MediaDataProcessorTest : SysuiTestCase() { sessionCallbackCaptor.value.invoke(KEY) // It remains as a regular player - verify(listener, never()).onMediaDataRemoved(eq(KEY)) + verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean()) verify(listener, never()) .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) } @@ -2118,7 +2118,7 @@ class MediaDataProcessorTest : SysuiTestCase() { mediaDataProcessor.onNotificationRemoved(KEY) // It is fully removed - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) verify(listener, never()) .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) @@ -2171,7 +2171,7 @@ class MediaDataProcessorTest : SysuiTestCase() { mediaDataProcessor.onNotificationRemoved(KEY) // It remains as a regular player - verify(listener, never()).onMediaDataRemoved(eq(KEY)) + verify(listener, never()).onMediaDataRemoved(eq(KEY), anyBoolean()) verify(listener, never()) .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) } @@ -2224,7 +2224,7 @@ class MediaDataProcessorTest : SysuiTestCase() { sessionCallbackCaptor.value.invoke(KEY) // It is fully removed - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) verify(listener, never()) .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) @@ -2278,7 +2278,7 @@ class MediaDataProcessorTest : SysuiTestCase() { sessionCallbackCaptor.value.invoke(KEY) // It is fully removed. - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) verify(listener, never()) .onMediaDataLoaded( @@ -2304,7 +2304,7 @@ class MediaDataProcessorTest : SysuiTestCase() { sessionCallbackCaptor.value.invoke(KEY) // It is fully removed - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) verify(logger).logMediaRemoved(anyInt(), eq(PACKAGE_NAME), eq(data.instanceId)) verify(listener, never()) .onMediaDataLoaded(eq(PACKAGE_NAME), any(), any(), anyBoolean(), anyInt(), anyBoolean()) @@ -2354,7 +2354,7 @@ class MediaDataProcessorTest : SysuiTestCase() { mediaDataProcessor.onNotificationRemoved(KEY) // We still make sure to remove it - verify(listener).onMediaDataRemoved(eq(KEY)) + verify(listener).onMediaDataRemoved(eq(KEY), eq(false)) } @Test 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 a447e442a384..befe64c1f411 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 @@ -60,6 +60,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.any @@ -158,7 +159,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun removeUnknown() { - manager.onMediaDataRemoved("unknown") + manager.onMediaDataRemoved("unknown", false) + verify(listener, never()).onKeyRemoved(eq(KEY), anyBoolean()) } @Test @@ -170,7 +172,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun loadAndRemoveMediaData() { manager.onMediaDataLoaded(KEY, null, mediaData) - manager.onMediaDataRemoved(KEY) + manager.onMediaDataRemoved(KEY, false) fakeBgExecutor.runAllReady() verify(lmm).unregisterCallback(any()) verify(muteAwaitManager).stopListening() @@ -386,9 +388,9 @@ public class MediaDeviceManagerTest : SysuiTestCase() { fun listenerReceivesKeyRemoved() { manager.onMediaDataLoaded(KEY, null, mediaData) // WHEN the notification is removed - manager.onMediaDataRemoved(KEY) + manager.onMediaDataRemoved(KEY, true) // THEN the listener receives key removed event - verify(listener).onKeyRemoved(eq(KEY)) + verify(listener).onKeyRemoved(eq(KEY), eq(true)) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt index 5a3c220b3d23..030bca25c518 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt @@ -165,10 +165,10 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { @Test fun noMediaSession_removedEventNotFiltered() { - filter.onMediaDataRemoved(KEY) + filter.onMediaDataRemoved(KEY, false) bgExecutor.runAllReady() fgExecutor.runAllReady() - verify(mediaListener).onMediaDataRemoved(eq(KEY)) + verify(mediaListener).onMediaDataRemoved(eq(KEY), eq(false)) } @Test @@ -193,11 +193,11 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { whenever(mediaSessionManager.getActiveSessions(any())).thenReturn(controllers) sessionListener.onActiveSessionsChanged(controllers) // WHEN a removed event is received - filter.onMediaDataRemoved(KEY) + filter.onMediaDataRemoved(KEY, false) bgExecutor.runAllReady() fgExecutor.runAllReady() // THEN the event is not filtered - verify(mediaListener).onMediaDataRemoved(eq(KEY)) + verify(mediaListener).onMediaDataRemoved(eq(KEY), eq(false)) } @Test @@ -294,7 +294,7 @@ public class MediaSessionBasedFilterTest : SysuiTestCase() { anyBoolean() ) // AND there should be a removed event for key2 - verify(mediaListener).onMediaDataRemoved(eq(key2)) + verify(mediaListener).onMediaDataRemoved(eq(key2), eq(false)) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt index 3cc65c9524a8..cdbf9d757ec1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt @@ -166,12 +166,12 @@ class MediaTimeoutListenerTest : SysuiTestCase() { @Test fun testOnMediaDataRemoved_unregistersPlaybackListener() { mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) - mediaTimeoutListener.onMediaDataRemoved(KEY) + mediaTimeoutListener.onMediaDataRemoved(KEY, false) verify(mediaController).unregisterCallback(anyObject()) // Ignores duplicate requests clearInvocations(mediaController) - mediaTimeoutListener.onMediaDataRemoved(KEY) + mediaTimeoutListener.onMediaDataRemoved(KEY, false) verify(mediaController, never()).unregisterCallback(anyObject()) } @@ -181,7 +181,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData) assertThat(executor.numPending()).isEqualTo(1) // WHEN the media is removed - mediaTimeoutListener.onMediaDataRemoved(KEY) + mediaTimeoutListener.onMediaDataRemoved(KEY, false) // THEN the timeout runnable is cancelled assertThat(executor.numPending()).isEqualTo(0) } @@ -398,7 +398,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() { // WHEN we have a resume control testOnMediaDataLoaded_resumption_registersTimeout() // AND the media is removed - mediaTimeoutListener.onMediaDataRemoved(PACKAGE) + mediaTimeoutListener.onMediaDataRemoved(PACKAGE, false) // THEN the timeout runnable is cancelled assertThat(executor.numPending()).isEqualTo(0) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt index 0a5aace91481..3bb8b8fcd775 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt @@ -33,6 +33,8 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.SysuiTestCase import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.Flags +import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState @@ -74,12 +76,14 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestDispatcher import kotlinx.coroutines.test.UnconfinedTestDispatcher 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.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito.anyLong import org.mockito.Mockito.floatThat import org.mockito.Mockito.mock import org.mockito.Mockito.never @@ -136,6 +140,9 @@ class MediaCarouselControllerTest : SysuiTestCase() { private lateinit var testDispatcher: TestDispatcher private lateinit var mediaCarouselController: MediaCarouselController + private var originalResumeSetting = + Settings.Secure.getInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 1) + @Before fun setup() { MockitoAnnotations.initMocks(this) @@ -186,6 +193,15 @@ class MediaCarouselControllerTest : SysuiTestCase() { ) } + @After + fun tearDown() { + Settings.Secure.putInt( + context.contentResolver, + Settings.Secure.MEDIA_CONTROLS_RESUME, + originalResumeSetting + ) + } + @Test fun testPlayerOrdering() { // Test values: key, data, last active time @@ -822,6 +838,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Test fun testKeyguardGone_showMediaCarousel() = kosmos.testScope.runTest { + kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) var updatedVisibility = false mediaCarouselController.updateHostVisibility = { updatedVisibility = true } mediaCarouselController.mediaCarousel = mediaCarousel @@ -844,6 +861,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Test fun keyguardShowing_notAllowedOnLockscreen_updateVisibility() { kosmos.testScope.runTest { + kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) var updatedVisibility = false mediaCarouselController.updateHostVisibility = { updatedVisibility = true } mediaCarouselController.mediaCarousel = mediaCarousel @@ -870,6 +888,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { @Test fun keyguardShowing_allowedOnLockscreen_updateVisibility() { kosmos.testScope.runTest { + kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) var updatedVisibility = false mediaCarouselController.updateHostVisibility = { updatedVisibility = true } mediaCarouselController.mediaCarousel = mediaCarousel @@ -968,6 +987,45 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(panel).updateAnimatorDurationScale() } + @Test + fun swipeToDismiss_pausedAndResumeOff_userInitiated() { + // When resumption is disabled, paused media should be dismissed after being swiped away + Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0) + + val pausedMedia = DATA.copy(isPlaying = false) + listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, pausedMedia) + mediaCarouselController.onSwipeToDismiss() + + // When it can be removed immediately on update + whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(true) + val inactiveMedia = pausedMedia.copy(active = false) + listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, inactiveMedia) + + // This is processed as a user-initiated dismissal + verify(debugLogger).logMediaRemoved(eq(PAUSED_LOCAL), eq(true)) + verify(mediaDataManager).dismissMediaData(eq(PAUSED_LOCAL), anyLong(), eq(true)) + } + + @Test + fun swipeToDismiss_pausedAndResumeOff_delayed_userInitiated() { + // When resumption is disabled, paused media should be dismissed after being swiped away + Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0) + mediaCarouselController.updateHostVisibility = {} + + val pausedMedia = DATA.copy(isPlaying = false) + listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, pausedMedia) + mediaCarouselController.onSwipeToDismiss() + + // When it can't be removed immediately on update + whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(false) + val inactiveMedia = pausedMedia.copy(active = false) + listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, inactiveMedia) + visualStabilityCallback.value.onReorderingAllowed() + + // This is processed as a user-initiated dismissal + verify(mediaDataManager).dismissMediaData(eq(PAUSED_LOCAL), anyLong(), eq(true)) + } + /** * Helper method when a configuration change occurs. * diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt index 83e4d3130b67..0c9fee9e2741 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt @@ -1344,7 +1344,7 @@ public class MediaControlPanelTest : SysuiTestCase() { assertThat(dismiss.isEnabled).isEqualTo(true) dismiss.callOnClick() verify(logger).logLongPressDismiss(anyInt(), eq(PACKAGE), eq(instanceId)) - verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong()) + verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong(), eq(true)) } @Test @@ -1360,7 +1360,8 @@ public class MediaControlPanelTest : SysuiTestCase() { @Test fun player_dismissButtonClick_notInManager() { val mediaKey = "key for dismissal" - whenever(mediaDataManager.dismissMediaData(eq(mediaKey), anyLong())).thenReturn(false) + whenever(mediaDataManager.dismissMediaData(eq(mediaKey), anyLong(), eq(true))) + .thenReturn(false) player.attachPlayer(viewHolder) val state = mediaData.copy(notificationKey = KEY) @@ -1369,8 +1370,8 @@ public class MediaControlPanelTest : SysuiTestCase() { assertThat(dismiss.isEnabled).isEqualTo(true) dismiss.callOnClick() - verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong()) - verify(mediaCarouselController).removePlayer(eq(mediaKey), eq(false), eq(false)) + verify(mediaDataManager).dismissMediaData(eq(mediaKey), anyLong(), eq(true)) + verify(mediaCarouselController).removePlayer(eq(mediaKey), eq(false), eq(false), eq(true)) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java index ff7c970960e9..8f8630e90694 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java @@ -104,11 +104,11 @@ public class MediaDreamSentinelTest extends SysuiTestCase { listener.onMediaDataLoaded(mKey, mOldKey, mData, /* immediately= */true, /* receivedSmartspaceCardLatency= */0, /* isSsReactived= */ false); - listener.onMediaDataRemoved(mKey); + listener.onMediaDataRemoved(mKey, false); verify(mDreamOverlayStateController, never()).removeComplication(any()); when(mMediaDataManager.hasActiveMedia()).thenReturn(false); - listener.onMediaDataRemoved(mKey); + listener.onMediaDataRemoved(mKey, false); verify(mDreamOverlayStateController).removeComplication(eq(mMediaEntryComplication)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt index 8e0541008f59..c06a28e3d840 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt @@ -42,13 +42,13 @@ class SysUiStateExtTest : SysuiTestCase() { fun updateFlags() { underTest.updateFlags( Display.DEFAULT_DISPLAY, - 1 to true, - 2 to false, - 3 to true, + 1L to true, + 2L to false, + 3L to true, ) - assertThat(underTest.flags and 1).isNotEqualTo(0) - assertThat(underTest.flags and 2).isEqualTo(0) - assertThat(underTest.flags and 3).isNotEqualTo(0) + assertThat(underTest.flags and 1L).isNotEqualTo(0L) + assertThat(underTest.flags and 2L).isEqualTo(0L) + assertThat(underTest.flags and 3L).isNotEqualTo(0L) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java index 224e75514ef6..2ff660f793cb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java @@ -125,7 +125,7 @@ public class NavBarHelperTest extends SysuiTestCase { private AccessibilityManager.AccessibilityServicesStateChangeListener mAccessibilityServicesStateChangeListener; - private static final int ACCESSIBILITY_BUTTON_CLICKABLE_STATE = + private static final long ACCESSIBILITY_BUTTON_CLICKABLE_STATE = SYSUI_STATE_A11Y_BUTTON_CLICKABLE | SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; private NavBarHelper mNavBarHelper; diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index 0e7a21517913..6cea1e895e74 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -38,7 +38,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -300,7 +300,7 @@ public class NavigationBarTest extends SysuiTestCase { doNothing().when(mWindowManager).addView(any(), any()); doNothing().when(mWindowManager).removeViewImmediate(any()); mMockSysUiState = mock(SysUiState.class); - when(mMockSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mMockSysUiState); + when(mMockSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mMockSysUiState); mContext.addMockSystemService(WindowManager.class, mWindowManager); mSysuiTestableContextExternal.addMockSystemService(WindowManager.class, mWindowManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt index 8d01e80d37d4..bba275e993fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt @@ -16,18 +16,18 @@ import com.android.systemui.statusbar.phone.LightBarTransitionsController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.wm.shell.back.BackAnimation import com.android.wm.shell.pip.Pip +import java.util.Optional import org.junit.Before import org.junit.Test import org.mockito.ArgumentMatchers import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.any import org.mockito.Mockito.anyBoolean -import org.mockito.Mockito.anyInt +import org.mockito.Mockito.anyLong import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations -import java.util.Optional @SmallTest class TaskbarDelegateTest : SysuiTestCase() { @@ -74,7 +74,7 @@ class TaskbarDelegateTest : SysuiTestCase() { `when`(mNavBarHelper.edgeBackGestureHandler).thenReturn(mEdgeBackGestureHandler) `when`(mLightBarControllerFactory.create(any())).thenReturn(mLightBarTransitionController) `when`(mNavBarHelper.currentSysuiState).thenReturn(mCurrentSysUiState) - `when`(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState) + `when`(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState) mTaskStackChangeListeners = TaskStackChangeListeners.getTestInstance() mTaskbarDelegate = TaskbarDelegate(context, mLightBarControllerFactory, mStatusBarKeyguardViewManager) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java index f57f04000be9..68307b1b905e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java @@ -16,10 +16,12 @@ package com.android.systemui.qs.external; import static android.os.PowerExemptionManager.REASON_TILE_ONCLICK; +import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf; import static android.service.quicksettings.TileService.START_ACTIVITY_NEEDS_PENDING_INTENT; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; @@ -55,13 +57,15 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.IDeviceIdleController; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.FlagsParameterization; import android.service.quicksettings.IQSService; import android.service.quicksettings.IQSTileService; import android.service.quicksettings.TileService; import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; @@ -73,12 +77,24 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.mockito.Mockito; import org.mockito.MockitoSession; +import java.util.List; + +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + @SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(ParameterizedAndroidJunit4.class) public class TileLifecycleManagerTest extends SysuiTestCase { + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX); + } + private final PackageManagerAdapter mMockPackageManagerAdapter = mock(PackageManagerAdapter.class); private final BroadcastDispatcher mMockBroadcastDispatcher = @@ -98,6 +114,11 @@ public class TileLifecycleManagerTest extends SysuiTestCase { private TestContextWrapper mWrappedContext; private MockitoSession mMockitoSession; + public TileLifecycleManagerTest(FlagsParameterization flags) { + super(); + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setUp() throws Exception { setPackageEnabled(true); @@ -263,7 +284,8 @@ public class TileLifecycleManagerTest extends SysuiTestCase { } @Test - public void testNoClickOfNotListeningAnymore() throws Exception { + @DisableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX) + public void testNoClickIfNotListeningAnymore() throws Exception { mStateManager.onTileAdded(); mStateManager.onStartListening(); mStateManager.onClick(null); @@ -279,6 +301,42 @@ public class TileLifecycleManagerTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX) + public void testNoClickIfNotListeningBeforeClick() throws Exception { + mStateManager.onTileAdded(); + mStateManager.onStartListening(); + mStateManager.onStopListening(); + mStateManager.onClick(null); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); + + verifyBind(1); + mStateManager.executeSetBindService(false); + mExecutor.runAllReady(); + assertFalse(mContext.isBound(mTileServiceComponentName)); + verify(mMockTileService, never()).onClick(null); + } + + @Test + @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX) + public void testClickIfStopListeningBeforeProcessedClick() throws Exception { + mStateManager.onTileAdded(); + mStateManager.onStartListening(); + mStateManager.onClick(null); + mStateManager.onStopListening(); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); + + verifyBind(1); + mStateManager.executeSetBindService(false); + mExecutor.runAllReady(); + assertFalse(mContext.isBound(mTileServiceComponentName)); + InOrder inOrder = Mockito.inOrder(mMockTileService); + inOrder.verify(mMockTileService).onClick(null); + inOrder.verify(mMockTileService).onStopListening(); + } + + @Test public void testComponentEnabling() throws Exception { mStateManager.onTileAdded(); mStateManager.onStartListening(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java index 0ff29dbbfde7..1c86638c9f27 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java @@ -15,12 +15,18 @@ */ package com.android.systemui.qs.external; +import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf; + +import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX; +import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -32,16 +38,19 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; -import android.os.HandlerThread; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.FlagsParameterization; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository; import com.android.systemui.settings.UserTracker; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.After; import org.junit.Before; @@ -51,10 +60,20 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; + +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + @SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(ParameterizedAndroidJunit4.class) public class TileServiceManagerTest extends SysuiTestCase { + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX); + } + @Mock private TileServices mTileServices; @Mock @@ -68,17 +87,22 @@ public class TileServiceManagerTest extends SysuiTestCase { @Mock private CustomTileAddedRepository mCustomTileAddedRepository; - private HandlerThread mThread; - private Handler mHandler; + private FakeExecutor mFakeExecutor; + private TileServiceManager mTileServiceManager; private ComponentName mComponentName; + public TileServiceManagerTest(FlagsParameterization flags) { + super(); + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mThread = new HandlerThread("TestThread"); - mThread.start(); - mHandler = Handler.createAsync(mThread.getLooper()); + mFakeExecutor = new FakeExecutor(new FakeSystemClock()); + Handler handler = mockExecutorHandler(mFakeExecutor); + when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM); when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM); @@ -90,13 +114,12 @@ public class TileServiceManagerTest extends SysuiTestCase { mComponentName = new ComponentName(mContext, TileServiceManagerTest.class); when(mTileLifecycle.getComponent()).thenReturn(mComponentName); - mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mUserTracker, + mTileServiceManager = new TileServiceManager(mTileServices, handler, mUserTracker, mCustomTileAddedRepository, mTileLifecycle); } @After public void tearDown() throws Exception { - mThread.quit(); mTileServiceManager.handleDestroy(); } @@ -201,4 +224,59 @@ public class TileServiceManagerTest extends SysuiTestCase { verify(mTileLifecycle, times(2)).executeSetBindService(captor.capture()); assertFalse((boolean) captor.getValue()); } + + @Test + @DisableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX) + public void testStopListeningAndUnbindImmediatelyAfterUpdate() { + when(mTileLifecycle.isActiveTile()).thenReturn(true); + mTileServiceManager.startLifecycleManagerAndAddTile(); + mTileServiceManager.setBindAllowed(true); + clearInvocations(mTileLifecycle); + + mTileServiceManager.setBindRequested(true); + verify(mTileLifecycle).executeSetBindService(true); + + mTileServiceManager.setLastUpdate(0); + mFakeExecutor.advanceClockToLast(); + mFakeExecutor.runAllReady(); + verify(mTileLifecycle).onStopListening(); + verify(mTileLifecycle).executeSetBindService(false); + } + + @Test + @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX) + public void testStopListeningAndUnbindImmediatelyAfterUpdate_ifRequestedFromTileService() { + when(mTileLifecycle.isActiveTile()).thenReturn(true); + mTileServiceManager.startLifecycleManagerAndAddTile(); + mTileServiceManager.setBindAllowed(true); + clearInvocations(mTileLifecycle); + + mTileServiceManager.setBindRequested(true); + mTileServiceManager.onStartListeningFromRequest(); + verify(mTileLifecycle).onStartListening(); + + mTileServiceManager.setLastUpdate(0); + mFakeExecutor.advanceClockToLast(); + mFakeExecutor.runAllReady(); + verify(mTileLifecycle).onStopListening(); + verify(mTileLifecycle).executeSetBindService(false); + } + + @Test + @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX) + public void testNotUnbindImmediatelyAfterUpdate_ifRequestedFromSystemUI() { + when(mTileLifecycle.isActiveTile()).thenReturn(true); + mTileServiceManager.startLifecycleManagerAndAddTile(); + mTileServiceManager.setBindAllowed(true); + clearInvocations(mTileLifecycle); + + mTileServiceManager.setBindRequested(true); + // The tile requests startListening (because a click happened) + + mTileServiceManager.setLastUpdate(0); + mFakeExecutor.advanceClockToLast(); + mFakeExecutor.runAllReady(); + verify(mTileLifecycle, never()).onStopListening(); + verify(mTileLifecycle, never()).executeSetBindService(false); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java index b62d59d3a2f2..bcff88a49ad6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java @@ -15,6 +15,10 @@ */ package com.android.systemui.qs.external; +import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf; + +import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; @@ -33,8 +37,10 @@ import android.os.Binder; import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.FlagsParameterization; import android.service.quicksettings.IQSTileService; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; @@ -64,13 +70,23 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.List; import javax.inject.Provider; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(ParameterizedAndroidJunit4.class) @RunWithLooper public class TileServicesTest extends SysuiTestCase { + + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX); + } + private static int NUM_FAKES = TileServices.DEFAULT_MAX_BOUND * 2; private static final ComponentName TEST_COMPONENT = @@ -106,6 +122,11 @@ public class TileServicesTest extends SysuiTestCase { @Mock private CustomTileAddedRepository mCustomTileAddedRepository; + public TileServicesTest(FlagsParameterization flags) { + super(); + mSetFlagsRule.setFlagsParameterization(flags); + } + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); @@ -194,6 +215,7 @@ public class TileServicesTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX) public void testRequestListeningStatusCommand() throws RemoteException { ArgumentCaptor<CommandQueue.Callbacks> captor = ArgumentCaptor.forClass(CommandQueue.Callbacks.class); @@ -213,6 +235,26 @@ public class TileServicesTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX) + public void testRequestListeningStatusCommand_onStartListeningFromRequest() { + ArgumentCaptor<CommandQueue.Callbacks> captor = + ArgumentCaptor.forClass(CommandQueue.Callbacks.class); + verify(mCommandQueue).addCallback(captor.capture()); + + CustomTile mockTile = mock(CustomTile.class); + when(mockTile.getComponent()).thenReturn(TEST_COMPONENT); + + TileServiceManager manager = mTileService.getTileWrapper(mockTile); + when(manager.isActiveTile()).thenReturn(true); + when(manager.getTileService()).thenReturn(mock(IQSTileService.class)); + + captor.getValue().requestTileServiceListeningState(TEST_COMPONENT); + mTestableLooper.processAllMessages(); + verify(manager).setBindRequested(true); + verify(manager).onStartListeningFromRequest(); + } + + @Test public void testValidCustomTileStartsActivity() { CustomTile tile = mock(CustomTile.class); PendingIntent pi = mock(PendingIntent.class); @@ -263,6 +305,7 @@ public class TileServicesTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX) public void tileFreedForCorrectUser() throws RemoteException { verify(mCommandQueue).addCallback(mCallbacksArgumentCaptor.capture()); @@ -297,6 +340,42 @@ public class TileServicesTest extends SysuiTestCase { verify(manager1.getTileService()).onStartListening(); } + @Test + @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX) + public void tileFreedForCorrectUser_onStartListeningFromRequest() throws RemoteException { + verify(mCommandQueue).addCallback(mCallbacksArgumentCaptor.capture()); + + ComponentName componentName = new ComponentName("pkg", "cls"); + CustomTile tileUser0 = mock(CustomTile.class); + CustomTile tileUser1 = mock(CustomTile.class); + + when(tileUser0.getComponent()).thenReturn(componentName); + when(tileUser1.getComponent()).thenReturn(componentName); + when(tileUser0.getUser()).thenReturn(0); + when(tileUser1.getUser()).thenReturn(1); + + // Create a tile for user 0 + TileServiceManager manager0 = mTileService.getTileWrapper(tileUser0); + when(manager0.isActiveTile()).thenReturn(true); + // Then create a tile for user 1 + TileServiceManager manager1 = mTileService.getTileWrapper(tileUser1); + when(manager1.isActiveTile()).thenReturn(true); + + // When the tile for user 0 gets freed + mTileService.freeService(tileUser0, manager0); + // and the user is 1 + when(mUserTracker.getUserId()).thenReturn(1); + + // a call to requestListeningState + mCallbacksArgumentCaptor.getValue().requestTileServiceListeningState(componentName); + mTestableLooper.processAllMessages(); + + // will call in the correct tile + verify(manager1).setBindRequested(true); + // and set it to listening + verify(manager1).onStartListeningFromRequest(); + } + private class TestTileServices extends TileServices { TestTileServices(QSHost host, Provider<Handler> handlerProvider, BroadcastDispatcher broadcastDispatcher, UserTracker userTracker, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt new file mode 100644 index 000000000000..db752dd64997 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.domain.interactor + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository +import com.android.systemui.qs.panels.data.repository.IconTilesRepository +import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository +import com.android.systemui.qs.panels.data.repository.iconTilesRepository +import com.android.systemui.qs.panels.shared.model.GridLayoutType +import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType +import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository +import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidTestingRunner::class) +class GridConsistencyInteractorTest : SysuiTestCase() { + + data object TestGridLayoutType : GridLayoutType + + private val gridLayout: MutableStateFlow<GridLayoutType> = + MutableStateFlow(InfiniteGridLayoutType) + + private val iconOnlyTiles = + MutableStateFlow( + setOf( + TileSpec.create("smallA"), + TileSpec.create("smallB"), + TileSpec.create("smallC"), + TileSpec.create("smallD"), + TileSpec.create("smallE"), + ) + ) + + private val kosmos = + testKosmos().apply { + iconTilesRepository = + object : IconTilesRepository { + override val iconTilesSpecs: StateFlow<Set<TileSpec>> + get() = iconOnlyTiles.asStateFlow() + } + gridConsistencyInteractorsMap = + mapOf( + Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor), + Pair(TestGridLayoutType, noopGridConsistencyInteractor) + ) + gridLayoutTypeRepository = + object : GridLayoutTypeRepository { + override val layout: StateFlow<GridLayoutType> = gridLayout.asStateFlow() + } + } + + private val underTest = with(kosmos) { gridConsistencyInteractor } + + @Before + fun setUp() { + gridLayout.value = InfiniteGridLayoutType + underTest.start() + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun changeLayoutType_usesCorrectGridConsistencyInteractor() = + with(kosmos) { + testScope.runTest { + // Using the no-op grid consistency interactor + gridLayout.value = TestGridLayoutType + + // Setting an invalid layout with holes + // [ Large A ] [ sa ] + // [ Large B ] [ Large C ] + // [ sb ] [ Large D ] + val newTiles = + listOf( + TileSpec.create("largeA"), + TileSpec.create("smallA"), + TileSpec.create("largeB"), + TileSpec.create("largeC"), + TileSpec.create("smallB"), + TileSpec.create("largeD"), + ) + tileSpecRepository.setTiles(0, newTiles) + + runCurrent() + + val tiles = currentTilesInteractor.currentTiles.value + val tileSpecs = tiles.map { it.spec } + + // Saved tiles should be unchanged + assertThat(tileSpecs).isEqualTo(newTiles) + } + } + + @Test + fun validTilesWithInfiniteGridConsistencyInteractor_unchangedList() = + with(kosmos) { + testScope.runTest { + // Setting a valid layout with holes + // [ Large A ] [ sa ][ sb ] + // [ Large B ] [ Large C ] + // [ Large D ] + val newTiles = + listOf( + TileSpec.create("largeA"), + TileSpec.create("smallA"), + TileSpec.create("smallB"), + TileSpec.create("largeB"), + TileSpec.create("largeC"), + TileSpec.create("largeD"), + ) + tileSpecRepository.setTiles(0, newTiles) + + runCurrent() + + val tiles = currentTilesInteractor.currentTiles.value + val tileSpecs = tiles.map { it.spec } + + // Saved tiles should be unchanged + assertThat(tileSpecs).isEqualTo(newTiles) + } + } + + @Test + fun invalidTilesWithInfiniteGridConsistencyInteractor_savesNewList() = + with(kosmos) { + testScope.runTest { + // Setting an invalid layout with holes + // [ sa ] [ Large A ] + // [ Large B ] [ sb ] [ sc ] + // [ sd ] [ se ] [ Large C ] + val newTiles = + listOf( + TileSpec.create("smallA"), + TileSpec.create("largeA"), + TileSpec.create("largeB"), + TileSpec.create("smallB"), + TileSpec.create("smallC"), + TileSpec.create("smallD"), + TileSpec.create("smallE"), + TileSpec.create("largeC"), + ) + tileSpecRepository.setTiles(0, newTiles) + + runCurrent() + + val tiles = currentTilesInteractor.currentTiles.value + val tileSpecs = tiles.map { it.spec } + + // Expected grid + // [ sa ] [ Large A ] [ sb ] + // [ Large B ] [ sc ] [ sd ] + // [ se ] [ Large C ] + val expectedTiles = + listOf( + TileSpec.create("smallA"), + TileSpec.create("largeA"), + TileSpec.create("smallB"), + TileSpec.create("largeB"), + TileSpec.create("smallC"), + TileSpec.create("smallD"), + TileSpec.create("smallE"), + TileSpec.create("largeC"), + ) + + // Saved tiles should be unchanged + assertThat(tileSpecs).isEqualTo(expectedTiles) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt new file mode 100644 index 000000000000..bda48adbfcc3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.domain.interactor + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.panels.data.repository.IconTilesRepository +import com.android.systemui.qs.panels.data.repository.iconTilesRepository +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class InfiniteGridConsistencyInteractorTest : SysuiTestCase() { + + private val iconOnlyTiles = + MutableStateFlow( + setOf( + TileSpec.create("smallA"), + TileSpec.create("smallB"), + TileSpec.create("smallC"), + TileSpec.create("smallD"), + TileSpec.create("smallE"), + ) + ) + private val kosmos = + testKosmos().apply { + iconTilesRepository = + object : IconTilesRepository { + override val iconTilesSpecs: StateFlow<Set<TileSpec>> + get() = iconOnlyTiles.asStateFlow() + } + } + private val underTest = with(kosmos) { infiniteGridConsistencyInteractor } + + @Test + fun validTiles_returnsUnchangedList() = + with(kosmos) { + testScope.runTest { + // Original grid + // [ Large A ] [ sa ][ sb ] + // [ Large B ] [ Large C ] + // [ Large D ] + val tiles = + listOf( + TileSpec.create("largeA"), + TileSpec.create("smallA"), + TileSpec.create("smallB"), + TileSpec.create("largeB"), + TileSpec.create("largeC"), + TileSpec.create("largeD"), + ) + + val newTiles = underTest.reconcileTiles(tiles) + + assertThat(newTiles).isEqualTo(tiles) + } + } + + @Test + fun invalidTiles_moveIconTileForward() = + with(kosmos) { + testScope.runTest { + // Original grid + // [ Large A ] [ sa ] + // [ Large B ] [ Large C ] + // [ sb ] [ Large D ] + val tiles = + listOf( + TileSpec.create("largeA"), + TileSpec.create("smallA"), + TileSpec.create("largeB"), + TileSpec.create("largeC"), + TileSpec.create("smallB"), + TileSpec.create("largeD"), + ) + // Expected grid + // [ Large A ] [ sa ][ sb ] + // [ Large B ] [ Large C ] + // [ Large D ] + val expectedTiles = + listOf( + TileSpec.create("largeA"), + TileSpec.create("smallA"), + TileSpec.create("smallB"), + TileSpec.create("largeB"), + TileSpec.create("largeC"), + TileSpec.create("largeD"), + ) + + val newTiles = underTest.reconcileTiles(tiles) + + assertThat(newTiles).isEqualTo(expectedTiles) + } + } + + @Test + fun invalidTiles_moveIconTileBack() = + with(kosmos) { + testScope.runTest { + // Original grid + // [ sa ] [ Large A ] + // [ Large B ] [ Large C ] + // [ Large D ] + val tiles = + listOf( + TileSpec.create("smallA"), + TileSpec.create("largeA"), + TileSpec.create("largeB"), + TileSpec.create("largeC"), + TileSpec.create("largeD"), + ) + // Expected grid + // [ Large A ] [ Large B ] + // [ Large C ] [ Large D ] + // [ sa ] + val expectedTiles = + listOf( + TileSpec.create("largeA"), + TileSpec.create("largeB"), + TileSpec.create("largeC"), + TileSpec.create("largeD"), + TileSpec.create("smallA"), + ) + + val newTiles = underTest.reconcileTiles(tiles) + + assertThat(newTiles).isEqualTo(expectedTiles) + } + } + + @Test + fun invalidTiles_multipleCorrections() = + with(kosmos) { + testScope.runTest { + // Original grid + // [ sa ] [ Large A ] + // [ Large B ] [ sb ] [ sc ] + // [ sd ] [ se ] [ Large C ] + val tiles = + listOf( + TileSpec.create("smallA"), + TileSpec.create("largeA"), + TileSpec.create("largeB"), + TileSpec.create("smallB"), + TileSpec.create("smallC"), + TileSpec.create("smallD"), + TileSpec.create("smallE"), + TileSpec.create("largeC"), + ) + // Expected grid + // [ sa ] [ Large A ] [ sb ] + // [ Large B ] [ sc ] [ sd ] + // [ se ] [ Large C ] + val expectedTiles = + listOf( + TileSpec.create("smallA"), + TileSpec.create("largeA"), + TileSpec.create("smallB"), + TileSpec.create("largeB"), + TileSpec.create("smallC"), + TileSpec.create("smallD"), + TileSpec.create("smallE"), + TileSpec.create("largeC"), + ) + + val newTiles = underTest.reconcileTiles(tiles) + + assertThat(newTiles).isEqualTo(expectedTiles) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java index 2536a9335908..9798562ab5a8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java @@ -5,6 +5,7 @@ import static android.provider.Settings.Global.AIRPLANE_MODE_ON; import static android.telephony.SignalStrength.NUM_SIGNAL_STRENGTH_BINS; import static android.telephony.SignalStrength.SIGNAL_STRENGTH_GREAT; import static android.telephony.SignalStrength.SIGNAL_STRENGTH_POOR; +import static android.telephony.SubscriptionManager.PROFILE_CLASS_PROVISIONING; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.settingslib.wifi.WifiUtils.getHotspotIconResource; @@ -217,6 +218,8 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase { when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry); when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID}); when(SubscriptionManager.getDefaultDataSubscriptionId()).thenReturn(SUB_ID); + SubscriptionInfo info = mock(SubscriptionInfo.class); + when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(info); when(mToastFactory.createToast(any(), anyString(), anyString(), anyInt(), anyInt())) .thenReturn(mSystemUIToast); when(mSystemUIToast.getView()).thenReturn(mToastView); @@ -1083,19 +1086,34 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase { } @Test - public void hasActiveSubId_activeSubIdListIsEmpty_returnFalse() { - when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{}); + public void hasActiveSubIdOnDds_noDds_returnFalse() { + when(SubscriptionManager.getDefaultDataSubscriptionId()) + .thenReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID); + mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged(); - assertThat(mInternetDialogController.hasActiveSubId()).isFalse(); + assertThat(mInternetDialogController.hasActiveSubIdOnDds()).isFalse(); } @Test - public void hasActiveSubId_activeSubIdListNotEmpty_returnTrue() { - when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID}); + public void hasActiveSubIdOnDds_activeDds_returnTrue() { + mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged(); + + assertThat(mInternetDialogController.hasActiveSubIdOnDds()).isTrue(); + } + + @Test + public void hasActiveSubIdOnDds_activeDdsAndHasProvisioning_returnFalse() { + when(SubscriptionManager.getDefaultDataSubscriptionId()) + .thenReturn(SUB_ID); + SubscriptionInfo info = mock(SubscriptionInfo.class); + when(info.isEmbedded()).thenReturn(true); + when(info.getProfileClass()).thenReturn(PROFILE_CLASS_PROVISIONING); + when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(info); + mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged(); - assertThat(mInternetDialogController.hasActiveSubId()).isTrue(); + assertThat(mInternetDialogController.hasActiveSubIdOnDds()).isFalse(); } private String getResourcesString(String name) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java index 6f88891c92d7..aefcc87e79cc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java @@ -251,7 +251,7 @@ public class InternetDialogDelegateTest extends SysuiTestCase { // Mobile network should be gone if the list of active subscriptionId is null. when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(false); when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false); - when(mInternetDialogController.hasActiveSubId()).thenReturn(false); + when(mInternetDialogController.hasActiveSubIdOnDds()).thenReturn(false); mInternetDialogDelegate.updateDialog(true); @@ -336,7 +336,7 @@ public class InternetDialogDelegateTest extends SysuiTestCase { @Test public void updateDialog_mobileDataIsEnabled_checkMobileDataSwitch() { - doReturn(true).when(mInternetDialogController).hasActiveSubId(); + doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds(); when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true); when(mInternetDialogController.isMobileDataEnabled()).thenReturn(true); mMobileToggleSwitch.setChecked(false); @@ -348,7 +348,7 @@ public class InternetDialogDelegateTest extends SysuiTestCase { @Test public void updateDialog_mobileDataIsNotChanged_checkMobileDataSwitch() { - doReturn(true).when(mInternetDialogController).hasActiveSubId(); + doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds(); when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true); when(mInternetDialogController.isMobileDataEnabled()).thenReturn(false); mMobileToggleSwitch.setChecked(false); @@ -361,7 +361,7 @@ public class InternetDialogDelegateTest extends SysuiTestCase { @Test public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() { mInternetDialogDelegate.dismissDialog(); - doReturn(true).when(mInternetDialogController).hasActiveSubId(); + doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds(); createInternetDialog(); // The preconditions WiFi ON and Internet WiFi are already in setUp() doReturn(false).when(mInternetDialogController).activeNetworkIsCellular(); @@ -522,7 +522,7 @@ public class InternetDialogDelegateTest extends SysuiTestCase { public void updateDialog_showSecondaryDataSub() { mInternetDialogDelegate.dismissDialog(); doReturn(1).when(mInternetDialogController).getActiveAutoSwitchNonDdsSubId(); - doReturn(true).when(mInternetDialogController).hasActiveSubId(); + doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds(); doReturn(false).when(mInternetDialogController).isAirplaneModeEnabled(); createInternetDialog(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java index f88a5a0d9f41..b75b3188ae64 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java @@ -20,7 +20,7 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotSame; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -81,7 +81,7 @@ public class RearDisplayDialogControllerTest extends SysuiTestCase { public void setup() { MockitoAnnotations.initMocks(this); - when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState); + when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState); when(mSystemUIDialogFactory.create()).thenReturn(mSystemUIDialog); when(mSystemUIDialog.getContext()).thenReturn(mContext); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt index effae5f67f02..74deae323b5c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt @@ -72,7 +72,7 @@ import org.mockito.Mockito.any import org.mockito.Mockito.anyInt import org.mockito.Mockito.atLeast import org.mockito.Mockito.clearInvocations -import org.mockito.Mockito.intThat +import org.mockito.Mockito.longThat import org.mockito.Mockito.mock import org.mockito.Mockito.spy import org.mockito.Mockito.times @@ -162,7 +162,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { verify(overviewProxy) .onSystemUiStateChanged( - intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_AWAKE } + longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_AWAKE } ) } @@ -172,7 +172,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { verify(overviewProxy) .onSystemUiStateChanged( - intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_WAKING } + longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_WAKING } ) } @@ -182,7 +182,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { verify(overviewProxy) .onSystemUiStateChanged( - intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_ASLEEP } + longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_ASLEEP } ) } @@ -194,7 +194,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { verify(overviewProxy) .onSystemUiStateChanged( - intThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_GOING_TO_SLEEP } + longThat { it and SYSUI_STATE_WAKEFULNESS_MASK == WAKEFULNESS_GOING_TO_SLEEP } ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt index 6846c7227d9c..fcc6b4f093ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt @@ -55,6 +55,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyLong import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.spy @@ -94,7 +95,7 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { fun setup() { MockitoAnnotations.initMocks(this) whenever(dprLazy.get()).thenReturn(devicePolicyResolver) - whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState) + whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState) whenever(screenCaptureDisabledDialogDelegate.createSysUIDialog()) .thenReturn(screenCaptureDisabledDialog) whenever( diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt index 853e50a12ea5..896c3bf7547e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt @@ -37,6 +37,7 @@ import org.junit.Assert.assertNotNull import org.junit.Before import org.junit.runner.RunWith import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.kotlin.never import org.mockito.kotlin.verify @RunWith(AndroidTestingRunner::class) @@ -111,6 +112,47 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() { assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_CHOOSER) } + @Test + fun scrollChipClicked_callsOnClick() = runTest { + actionsProvider = createActionsProvider() + + val onScrollClick = mock<Runnable>() + val numActions = viewModel.actions.value.size + actionsProvider.onScrollChipReady(onScrollClick) + viewModel.actions.value[numActions].onClicked!!.invoke() + + verify(onScrollClick).run() + } + + @Test + fun scrollChipClicked_afterInvalidate_doesNothing() = runTest { + actionsProvider = createActionsProvider() + + val onScrollClick = mock<Runnable>() + val numActions = viewModel.actions.value.size + actionsProvider.onScrollChipReady(onScrollClick) + actionsProvider.onScrollChipInvalidated() + viewModel.actions.value[numActions].onClicked!!.invoke() + + verify(onScrollClick, never()).run() + } + + @Test + fun scrollChipClicked_afterUpdate_runsNewAction() = runTest { + actionsProvider = createActionsProvider() + + val onScrollClick = mock<Runnable>() + val onScrollClick2 = mock<Runnable>() + val numActions = viewModel.actions.value.size + actionsProvider.onScrollChipReady(onScrollClick) + actionsProvider.onScrollChipInvalidated() + actionsProvider.onScrollChipReady(onScrollClick2) + viewModel.actions.value[numActions].onClicked!!.invoke() + + verify(onScrollClick2).run() + verify(onScrollClick, never()).run() + } + private fun createActionsProvider(): ScreenshotActionsProvider { return DefaultScreenshotActionsProvider( context, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt index 537049c7b957..49a467e152ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -40,6 +40,7 @@ import com.android.systemui.communal.data.repository.fakeCommunalRepository import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.communal.ui.compose.CommunalContent import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.util.CommunalColors import com.android.systemui.coroutines.collectLastValue @@ -50,7 +51,6 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.scene.shared.model.sceneDataSourceDelegator import com.android.systemui.shade.domain.interactor.shadeInteractor -import com.android.systemui.statusbar.phone.SystemUIDialogFactory import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat @@ -83,9 +83,9 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { @Mock private lateinit var communalViewModel: CommunalViewModel @Mock private lateinit var powerManager: PowerManager - @Mock private lateinit var dialogFactory: SystemUIDialogFactory @Mock private lateinit var touchMonitor: TouchMonitor @Mock private lateinit var communalColors: CommunalColors + @Mock private lateinit var communalContent: CommunalContent private lateinit var ambientTouchComponentFactory: AmbientTouchComponent.Factory private lateinit var parentView: FrameLayout @@ -117,12 +117,12 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { GlanceableHubContainerController( communalInteractor, communalViewModel, - dialogFactory, keyguardInteractor, shadeInteractor, powerManager, communalColors, ambientTouchComponentFactory, + communalContent, kosmos.sceneDataSourceDelegator, ) } @@ -159,12 +159,12 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { GlanceableHubContainerController( communalInteractor, communalViewModel, - dialogFactory, keyguardInteractor, shadeInteractor, powerManager, communalColors, ambientTouchComponentFactory, + communalContent, kosmos.sceneDataSourceDelegator, ) @@ -303,12 +303,12 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { GlanceableHubContainerController( communalInteractor, communalViewModel, - dialogFactory, keyguardInteractor, shadeInteractor, powerManager, communalColors, ambientTouchComponentFactory, + communalContent, kosmos.sceneDataSourceDelegator, ) @@ -322,12 +322,12 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { GlanceableHubContainerController( communalInteractor, communalViewModel, - dialogFactory, keyguardInteractor, shadeInteractor, powerManager, communalColors, ambientTouchComponentFactory, + communalContent, kosmos.sceneDataSourceDelegator, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 3793970394a8..766113f09308 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -446,6 +446,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mUiEventLogger, () -> mKosmos.getInteractionJankMonitor(), mJavaAdapter, + () -> mKeyguardTransitionInteractor, () -> mShadeInteractor, () -> mKosmos.getDeviceUnlockedInteractor(), () -> mKosmos.getSceneInteractor(), @@ -600,6 +601,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { new UiEventLoggerFake(), () -> mKosmos.getInteractionJankMonitor(), mJavaAdapter, + () -> mKeyguardTransitionInteractor, () -> mShadeInteractor, () -> mKosmos.getDeviceUnlockedInteractor(), () -> mKosmos.getSceneInteractor(), @@ -653,7 +655,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { when(mView.getParent()).thenReturn(mViewParent); when(mQs.getHeader()).thenReturn(mQsHeader); when(mDownMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_DOWN); - when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState); + when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState); mMainHandler = new Handler(Looper.getMainLooper()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index a867b0f7df00..45d0102f2dd1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -102,7 +102,7 @@ import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(ParameterizedAndroidJunit4::class) @RunWithLooper(setAsMainLooper = true) -class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization?) : SysuiTestCase() { +class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : SysuiTestCase() { @Mock private lateinit var view: NotificationShadeWindowView @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController @@ -160,7 +160,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization?) : private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java index 04fa5904d2e7..845744a54791 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java @@ -42,13 +42,10 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FakeFeatureFlagsClassic; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.keyguard.data.repository.FakeCommandQueue; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; -import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor; -import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.kosmos.KosmosJavaAdapter; @@ -59,9 +56,7 @@ import com.android.systemui.plugins.qs.QS; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.qs.QSFragmentLegacy; import com.android.systemui.res.R; -import com.android.systemui.scene.data.repository.SceneContainerRepository; import com.android.systemui.scene.domain.interactor.SceneInteractor; -import com.android.systemui.scene.shared.logger.SceneLogger; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.shade.domain.interactor.ShadeInteractor; @@ -176,12 +171,7 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { protected Handler mMainHandler; protected LockscreenShadeTransitionController.Callback mLockscreenShadeTransitionCallback; - protected final ShadeExpansionStateManager mShadeExpansionStateManager = - new ShadeExpansionStateManager(); - protected FragmentHostManager.FragmentListener mFragmentListener; - private FromLockscreenTransitionInteractor mFromLockscreenTransitionInteractor; - private FromPrimaryBouncerTransitionInteractor mFromPrimaryBouncerTransitionInteractor; @Before public void setup() { @@ -190,19 +180,11 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { mStatusBarStateController = mKosmos.getStatusBarStateController(); mKosmos.getFakeDeviceProvisioningRepository().setDeviceProvisioned(true); - FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic(); FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository(); PowerInteractor powerInteractor = mKosmos.getPowerInteractor(); - SceneInteractor sceneInteractor = new SceneInteractor( - mTestScope.getBackgroundScope(), - new SceneContainerRepository( - mTestScope.getBackgroundScope(), - mKosmos.getFakeSceneContainerConfig(), - mKosmos.getSceneDataSource()), - mock(SceneLogger.class), - mKosmos.getDeviceUnlockedInteractor()); + SceneInteractor sceneInteractor = mKosmos.getSceneInteractor(); KeyguardTransitionInteractor keyguardTransitionInteractor = mKosmos.getKeyguardTransitionInteractor(); @@ -220,10 +202,6 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { () -> mKosmos.getSharedNotificationContainerInteractor(), mTestScope); - mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor(); - mFromPrimaryBouncerTransitionInteractor = - mKosmos.getFromPrimaryBouncerTransitionInteractor(); - ResourcesSplitShadeStateController splitShadeStateController = new ResourcesSplitShadeStateController(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt index 347620a1631a..83ad18b6468b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt @@ -55,7 +55,7 @@ import platform.test.runner.parameterized.Parameters @RunWith(ParameterizedAndroidJunit4::class) @SmallTest @EnableFlags(FooterViewRefactor.FLAG_NAME) -class FooterViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { +class FooterViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos().apply { fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } @@ -79,7 +79,7 @@ class FooterViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt new file mode 100644 index 000000000000..5e50af39203f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/HeadsUpStyleProviderImplTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.row + +import android.app.Flags.FLAG_COMPACT_HEADS_UP_NOTIFICATION +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class HeadsUpStyleProviderImplTest : SysuiTestCase() { + + @Rule @JvmField val setFlagsRule = SetFlagsRule() + + private lateinit var statusBarModeRepositoryStore: FakeStatusBarModeRepository + private lateinit var headsUpStyleProvider: HeadsUpStyleProviderImpl + + @Before + fun setUp() { + statusBarModeRepositoryStore = FakeStatusBarModeRepository() + statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value = true + + headsUpStyleProvider = HeadsUpStyleProviderImpl(statusBarModeRepositoryStore) + } + + @Test + @DisableFlags(FLAG_COMPACT_HEADS_UP_NOTIFICATION) + fun shouldApplyCompactStyle_returnsFalse_whenCompactFlagDisabled() { + assertThat(headsUpStyleProvider.shouldApplyCompactStyle()).isFalse() + } + + @Test + @EnableFlags(FLAG_COMPACT_HEADS_UP_NOTIFICATION) + fun shouldApplyCompactStyle_returnsTrue_whenImmersiveModeEnabled() { + // GIVEN + statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value = true + + // THEN + assertThat(headsUpStyleProvider.shouldApplyCompactStyle()).isTrue() + } + + @Test + @EnableFlags(FLAG_COMPACT_HEADS_UP_NOTIFICATION) + fun shouldApplyCompactStyle_returnsFalse_whenImmersiveModeDisabled() { + // GIVEN + statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value = false + + // THEN + assertThat(headsUpStyleProvider.shouldApplyCompactStyle()).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt index 4f0f91a7ee56..926c35f32967 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackStateAnimatorTest.kt @@ -134,7 +134,8 @@ class StackStateAnimatorTest : SysuiTestCase() { /* isHeadsUpAnimation= */ eq(true), /* onStartedRunnable= */ any(), /* onFinishedRunnable= */ runnableCaptor.capture(), - /* animationListener= */ any() + /* animationListener= */ any(), + /* clipSide= */ eq(ExpandableView.ClipSide.BOTTOM), ) animatorTestRule.advanceTimeBy(disappearDuration) // move to the end of SSA animations diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java index c08860906d0b..fe6a88d00374 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java @@ -51,6 +51,7 @@ import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeHeaderController; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; @@ -80,6 +81,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { @Mock private QuickSettingsController mQuickSettingsController; @Mock private ShadeViewController mShadeViewController; @Mock private PanelExpansionInteractor mPanelExpansionInteractor; + @Mock private Lazy<ShadeInteractor> mShadeInteractorLazy; @Mock private ShadeHeaderController mShadeHeaderController; @Mock private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; private final MetricsLogger mMetricsLogger = new FakeMetricsLogger(); @@ -115,6 +117,7 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { mShadeController, mCommandQueue, mPanelExpansionInteractor, + mShadeInteractorLazy, mShadeHeaderController, mRemoteInputQuickSettingsDisabler, mMetricsLogger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt index 9b4f9312a7ba..cb40f72ff1af 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt @@ -66,7 +66,7 @@ import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) @TestableLooper.RunWithLooper -class KeyguardBypassControllerTest(flags: FlagsParameterization?) : SysuiTestCase() { +class KeyguardBypassControllerTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val featureFlags = FakeFeatureFlags() @@ -92,7 +92,7 @@ class KeyguardBypassControllerTest(flags: FlagsParameterization?) : SysuiTestCas } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Captor diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index b5525b1ce8e9..36df61d287a1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -295,6 +295,50 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } @Test + fun subscriptions_subIsOnlyNtn_modelHasExclusivelyNtnTrue() = + testScope.runTest { + val latest by collectLastValue(underTest.subscriptions) + + val onlyNtnSub = + mock<SubscriptionInfo>().also { + whenever(it.isOnlyNonTerrestrialNetwork).thenReturn(true) + whenever(it.subscriptionId).thenReturn(45) + whenever(it.groupUuid).thenReturn(GROUP_1) + whenever(it.carrierName).thenReturn("NTN only") + whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET) + } + + whenever(subscriptionManager.completeActiveSubscriptionInfoList) + .thenReturn(listOf(onlyNtnSub)) + getSubscriptionCallback().onSubscriptionsChanged() + + assertThat(latest).hasSize(1) + assertThat(latest!![0].isExclusivelyNonTerrestrial).isTrue() + } + + @Test + fun subscriptions_subIsNotOnlyNtn_modelHasExclusivelyNtnFalse() = + testScope.runTest { + val latest by collectLastValue(underTest.subscriptions) + + val notOnlyNtnSub = + mock<SubscriptionInfo>().also { + whenever(it.isOnlyNonTerrestrialNetwork).thenReturn(false) + whenever(it.subscriptionId).thenReturn(45) + whenever(it.groupUuid).thenReturn(GROUP_1) + whenever(it.carrierName).thenReturn("NTN only") + whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET) + } + + whenever(subscriptionManager.completeActiveSubscriptionInfoList) + .thenReturn(listOf(notOnlyNtnSub)) + getSubscriptionCallback().onSubscriptionsChanged() + + assertThat(latest).hasSize(1) + assertThat(latest!![0].isExclusivelyNonTerrestrial).isFalse() + } + + @Test fun testSubscriptions_carrierMergedOnly_listHasCarrierMerged() = testScope.runTest { val latest by collectLastValue(underTest.subscriptions) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt index 0b14be1eefbd..0f9cbfa66b5b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt @@ -42,14 +42,11 @@ import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import java.util.UUID import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.yield -import org.junit.After import org.junit.Before import org.junit.Test import org.mockito.Mock @@ -68,7 +65,7 @@ class MobileIconsInteractorTest : SysuiTestCase() { set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true) } - private val testDispatcher = UnconfinedTestDispatcher() + private val testDispatcher = StandardTestDispatcher() private val testScope = TestScope(testDispatcher) private val tableLogBuffer = @@ -113,17 +110,12 @@ class MobileIconsInteractorTest : SysuiTestCase() { ) } - @After fun tearDown() {} - @Test fun filteredSubscriptions_default() = testScope.runTest { - var latest: List<SubscriptionModel>? = null - val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.filteredSubscriptions) assertThat(latest).isEqualTo(listOf<SubscriptionModel>()) - - job.cancel() } // Based on the logic from the old pipeline, we'll never filter subs when there are more than 2 @@ -133,12 +125,9 @@ class MobileIconsInteractorTest : SysuiTestCase() { connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP)) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID) - var latest: List<SubscriptionModel>? = null - val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.filteredSubscriptions) assertThat(latest).isEqualTo(listOf(SUB_1, SUB_3_OPP, SUB_4_OPP)) - - job.cancel() } @Test @@ -146,12 +135,9 @@ class MobileIconsInteractorTest : SysuiTestCase() { testScope.runTest { connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) - var latest: List<SubscriptionModel>? = null - val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.filteredSubscriptions) assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2)) - - job.cancel() } @Test @@ -160,12 +146,9 @@ class MobileIconsInteractorTest : SysuiTestCase() { connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP)) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) - var latest: List<SubscriptionModel>? = null - val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.filteredSubscriptions) assertThat(latest).isEqualTo(listOf(SUB_3_OPP, SUB_4_OPP)) - - job.cancel() } @Test @@ -180,12 +163,9 @@ class MobileIconsInteractorTest : SysuiTestCase() { connectionsRepository.setSubscriptions(listOf(sub1, sub2)) connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID) - var latest: List<SubscriptionModel>? = null - val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.filteredSubscriptions) assertThat(latest).isEqualTo(listOf(sub1, sub2)) - - job.cancel() } @Test @@ -202,13 +182,10 @@ class MobileIconsInteractorTest : SysuiTestCase() { whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(false) - var latest: List<SubscriptionModel>? = null - val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.filteredSubscriptions) // Filtered subscriptions should show the active one when the config is false assertThat(latest).isEqualTo(listOf(sub3)) - - job.cancel() } @Test @@ -225,13 +202,10 @@ class MobileIconsInteractorTest : SysuiTestCase() { whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(false) - var latest: List<SubscriptionModel>? = null - val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.filteredSubscriptions) // Filtered subscriptions should show the active one when the config is false assertThat(latest).isEqualTo(listOf(sub4)) - - job.cancel() } @Test @@ -248,14 +222,11 @@ class MobileIconsInteractorTest : SysuiTestCase() { whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(true) - var latest: List<SubscriptionModel>? = null - val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.filteredSubscriptions) // Filtered subscriptions should show the primary (non-opportunistic) if the config is // true assertThat(latest).isEqualTo(listOf(sub1)) - - job.cancel() } @Test @@ -272,14 +243,11 @@ class MobileIconsInteractorTest : SysuiTestCase() { whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(true) - var latest: List<SubscriptionModel>? = null - val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.filteredSubscriptions) // Filtered subscriptions should show the primary (non-opportunistic) if the config is // true assertThat(latest).isEqualTo(listOf(sub1)) - - job.cancel() } @Test @@ -297,12 +265,9 @@ class MobileIconsInteractorTest : SysuiTestCase() { whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(false) - var latest: List<SubscriptionModel>? = null - val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.filteredSubscriptions) assertThat(latest).isEqualTo(listOf(sub3)) - - job.cancel() } @Test @@ -320,12 +285,9 @@ class MobileIconsInteractorTest : SysuiTestCase() { whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault) .thenReturn(false) - var latest: List<SubscriptionModel>? = null - val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.filteredSubscriptions) assertThat(latest).isEqualTo(listOf(sub1)) - - job.cancel() } @Test @@ -446,313 +408,345 @@ class MobileIconsInteractorTest : SysuiTestCase() { } @Test + fun filteredSubscriptions_subNotExclusivelyNonTerrestrial_hasSub() = + testScope.runTest { + val notExclusivelyNonTerrestrialSub = + SubscriptionModel( + isExclusivelyNonTerrestrial = false, + subscriptionId = 5, + carrierName = "Carrier 5", + profileClass = PROFILE_CLASS_UNSET, + ) + + connectionsRepository.setSubscriptions(listOf(notExclusivelyNonTerrestrialSub)) + + val latest by collectLastValue(underTest.filteredSubscriptions) + + assertThat(latest).isEqualTo(listOf(notExclusivelyNonTerrestrialSub)) + } + + @Test + fun filteredSubscriptions_subExclusivelyNonTerrestrial_doesNotHaveSub() = + testScope.runTest { + val exclusivelyNonTerrestrialSub = + SubscriptionModel( + isExclusivelyNonTerrestrial = true, + subscriptionId = 5, + carrierName = "Carrier 5", + profileClass = PROFILE_CLASS_UNSET, + ) + + connectionsRepository.setSubscriptions(listOf(exclusivelyNonTerrestrialSub)) + + val latest by collectLastValue(underTest.filteredSubscriptions) + + assertThat(latest).isEmpty() + } + + @Test + fun filteredSubscription_mixOfExclusivelyNonTerrestrialAndOther_hasOtherSubsOnly() = + testScope.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) + ) + + val latest by collectLastValue(underTest.filteredSubscriptions) + + assertThat(latest).isEqualTo(listOf(otherSub1, otherSub2)) + } + + @Test + fun filteredSubscriptions_exclusivelyNonTerrestrialSub_andOpportunistic_bothFiltersHappen() = + testScope.runTest { + // Exclusively non-terrestrial sub + val exclusivelyNonTerrestrialSub = + SubscriptionModel( + isExclusivelyNonTerrestrial = true, + subscriptionId = 5, + carrierName = "Carrier 5", + profileClass = PROFILE_CLASS_UNSET, + ) + + // Opportunistic subs + val (sub3, sub4) = + createSubscriptionPair( + subscriptionIds = Pair(SUB_3_ID, SUB_4_ID), + opportunistic = Pair(true, true), + grouped = true, + ) + + // WHEN both an exclusively non-terrestrial sub and opportunistic sub pair is included + connectionsRepository.setSubscriptions(listOf(sub3, sub4, exclusivelyNonTerrestrialSub)) + connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID) + + val latest by collectLastValue(underTest.filteredSubscriptions) + + // THEN both the only-non-terrestrial sub and the non-active sub are filtered out, + // leaving only sub3. + assertThat(latest).isEqualTo(listOf(sub3)) + } + + @Test fun activeDataConnection_turnedOn() = testScope.runTest { CONNECTION_1.setDataEnabled(true) - var latest: Boolean? = null - val job = - underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this) - assertThat(latest).isTrue() + val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled) - job.cancel() + assertThat(latest).isTrue() } @Test fun activeDataConnection_turnedOff() = testScope.runTest { CONNECTION_1.setDataEnabled(true) - var latest: Boolean? = null - val job = - underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled) CONNECTION_1.setDataEnabled(false) - yield() assertThat(latest).isFalse() - - job.cancel() } @Test fun activeDataConnection_invalidSubId() = testScope.runTest { - var latest: Boolean? = null - val job = - underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.activeDataConnectionHasDataEnabled) connectionsRepository.setActiveMobileDataSubscriptionId(INVALID_SUBSCRIPTION_ID) - yield() // An invalid active subId should tell us that data is off assertThat(latest).isFalse() - - job.cancel() } @Test fun failedConnection_default_validated_notFailed() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.mobileIsDefault.value = true connectionsRepository.defaultConnectionIsValidated.value = true - yield() assertThat(latest).isFalse() - - job.cancel() } @Test fun failedConnection_notDefault_notValidated_notFailed() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.mobileIsDefault.value = false connectionsRepository.defaultConnectionIsValidated.value = false - yield() assertThat(latest).isFalse() - - job.cancel() } @Test fun failedConnection_default_notValidated_failed() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.mobileIsDefault.value = true connectionsRepository.defaultConnectionIsValidated.value = false - yield() assertThat(latest).isTrue() - - job.cancel() } @Test fun failedConnection_carrierMergedDefault_notValidated_failed() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.hasCarrierMergedConnection.value = true connectionsRepository.defaultConnectionIsValidated.value = false - yield() assertThat(latest).isTrue() - - job.cancel() } /** Regression test for b/275076959. */ @Test fun failedConnection_dataSwitchInSameGroup_notFailed() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.mobileIsDefault.value = true connectionsRepository.defaultConnectionIsValidated.value = true + runCurrent() // WHEN there's a data change in the same subscription group connectionsRepository.activeSubChangedInGroupEvent.emit(Unit) connectionsRepository.defaultConnectionIsValidated.value = false + runCurrent() // THEN the default connection is *not* marked as failed because of forced validation assertThat(latest).isFalse() - - job.cancel() } @Test fun failedConnection_dataSwitchNotInSameGroup_isFailed() = testScope.runTest { - var latestConnectionFailed: Boolean? = null - val job = - underTest.isDefaultConnectionFailed - .onEach { latestConnectionFailed = it } - .launchIn(this) + val latest by collectLastValue(underTest.isDefaultConnectionFailed) + connectionsRepository.mobileIsDefault.value = true connectionsRepository.defaultConnectionIsValidated.value = true + runCurrent() // WHEN the connection is invalidated without a activeSubChangedInGroupEvent connectionsRepository.defaultConnectionIsValidated.value = false // THEN the connection is immediately marked as failed - assertThat(latestConnectionFailed).isTrue() - - job.cancel() + assertThat(latest).isTrue() } @Test fun alwaysShowDataRatIcon_configHasTrue() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.alwaysShowDataRatIcon) val config = MobileMappings.Config() config.alwaysShowDataRatIcon = true connectionsRepository.defaultDataSubRatConfig.value = config - yield() assertThat(latest).isTrue() - - job.cancel() } @Test fun alwaysShowDataRatIcon_configHasFalse() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.alwaysShowDataRatIcon.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.alwaysShowDataRatIcon) val config = MobileMappings.Config() config.alwaysShowDataRatIcon = false connectionsRepository.defaultDataSubRatConfig.value = config - yield() assertThat(latest).isFalse() - - job.cancel() } @Test fun alwaysUseCdmaLevel_configHasTrue() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.alwaysUseCdmaLevel) val config = MobileMappings.Config() config.alwaysShowCdmaRssi = true connectionsRepository.defaultDataSubRatConfig.value = config - yield() assertThat(latest).isTrue() - - job.cancel() } @Test fun alwaysUseCdmaLevel_configHasFalse() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.alwaysUseCdmaLevel.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.alwaysUseCdmaLevel) val config = MobileMappings.Config() config.alwaysShowCdmaRssi = false connectionsRepository.defaultDataSubRatConfig.value = config - yield() assertThat(latest).isFalse() - - job.cancel() } @Test fun isSingleCarrier_zeroSubscriptions_false() = testScope.runTest { - var latest: Boolean? = true - val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.isSingleCarrier) connectionsRepository.setSubscriptions(emptyList()) - assertThat(latest).isFalse() - job.cancel() + assertThat(latest).isFalse() } @Test fun isSingleCarrier_oneSubscription_true() = testScope.runTest { - var latest: Boolean? = false - val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.isSingleCarrier) connectionsRepository.setSubscriptions(listOf(SUB_1)) - assertThat(latest).isTrue() - job.cancel() + assertThat(latest).isTrue() } @Test fun isSingleCarrier_twoSubscriptions_false() = testScope.runTest { - var latest: Boolean? = true - val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.isSingleCarrier) connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) - assertThat(latest).isFalse() - job.cancel() + assertThat(latest).isFalse() } @Test fun isSingleCarrier_updates() = testScope.runTest { - var latest: Boolean? = false - val job = underTest.isSingleCarrier.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.isSingleCarrier) connectionsRepository.setSubscriptions(listOf(SUB_1)) assertThat(latest).isTrue() connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2)) assertThat(latest).isFalse() - - job.cancel() } @Test fun mobileIsDefault_mobileFalseAndCarrierMergedFalse_false() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.mobileIsDefault) connectionsRepository.mobileIsDefault.value = false connectionsRepository.hasCarrierMergedConnection.value = false assertThat(latest).isFalse() - - job.cancel() } @Test fun mobileIsDefault_mobileTrueAndCarrierMergedFalse_true() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.mobileIsDefault) connectionsRepository.mobileIsDefault.value = true connectionsRepository.hasCarrierMergedConnection.value = false assertThat(latest).isTrue() - - job.cancel() } /** Regression test for b/272586234. */ @Test fun mobileIsDefault_mobileFalseAndCarrierMergedTrue_true() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.mobileIsDefault) connectionsRepository.mobileIsDefault.value = false connectionsRepository.hasCarrierMergedConnection.value = true assertThat(latest).isTrue() - - job.cancel() } @Test fun mobileIsDefault_updatesWhenRepoUpdates() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.mobileIsDefault) connectionsRepository.mobileIsDefault.value = true assertThat(latest).isTrue() @@ -762,8 +756,6 @@ class MobileIconsInteractorTest : SysuiTestCase() { connectionsRepository.hasCarrierMergedConnection.value = true assertThat(latest).isTrue() - - job.cancel() } // The data switch tests are mostly testing the [forcingCellularValidation] flow, but that flow @@ -772,95 +764,79 @@ class MobileIconsInteractorTest : SysuiTestCase() { @Test fun dataSwitch_inSameGroup_validatedMatchesPreviousValue_expiresAfter2s() = testScope.runTest { - var latestConnectionFailed: Boolean? = null - val job = - underTest.isDefaultConnectionFailed - .onEach { latestConnectionFailed = it } - .launchIn(this) + 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 advanceTimeBy(1000) - assertThat(latestConnectionFailed).isFalse() + assertThat(latest).isFalse() // After 2s, the force validation expires so the connection updates to failed advanceTimeBy(1001) - assertThat(latestConnectionFailed).isTrue() - - job.cancel() + assertThat(latest).isTrue() } @Test fun dataSwitch_inSameGroup_notValidated_immediatelyMarkedAsFailed() = testScope.runTest { - var latestConnectionFailed: Boolean? = null - val job = - underTest.isDefaultConnectionFailed - .onEach { latestConnectionFailed = it } - .launchIn(this) + val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.mobileIsDefault.value = true connectionsRepository.defaultConnectionIsValidated.value = false + runCurrent() connectionsRepository.activeSubChangedInGroupEvent.emit(Unit) - assertThat(latestConnectionFailed).isTrue() - - job.cancel() + assertThat(latest).isTrue() } @Test fun dataSwitch_loseValidation_thenSwitchHappens_clearsForcedBit() = testScope.runTest { - var latestConnectionFailed: Boolean? = null - val job = - underTest.isDefaultConnectionFailed - .onEach { latestConnectionFailed = it } - .launchIn(this) + val latest by collectLastValue(underTest.isDefaultConnectionFailed) // GIVEN the network starts validated connectionsRepository.mobileIsDefault.value = true connectionsRepository.defaultConnectionIsValidated.value = true + runCurrent() // WHEN a data change happens in the same group connectionsRepository.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(latestConnectionFailed).isFalse() + assertThat(latest).isFalse() advanceTimeBy(1000) - assertThat(latestConnectionFailed).isFalse() + assertThat(latest).isFalse() // ... but expires after 2s advanceTimeBy(1001) - assertThat(latestConnectionFailed).isTrue() - - job.cancel() + assertThat(latest).isTrue() } @Test fun dataSwitch_whileAlreadyForcingValidation_resetsClock() = testScope.runTest { - var latestConnectionFailed: Boolean? = null - val job = - underTest.isDefaultConnectionFailed - .onEach { latestConnectionFailed = it } - .launchIn(this) + val latest by collectLastValue(underTest.isDefaultConnectionFailed) connectionsRepository.mobileIsDefault.value = true connectionsRepository.defaultConnectionIsValidated.value = true + runCurrent() connectionsRepository.activeSubChangedInGroupEvent.emit(Unit) @@ -869,44 +845,37 @@ class MobileIconsInteractorTest : SysuiTestCase() { // WHEN another change in same group event happens connectionsRepository.activeSubChangedInGroupEvent.emit(Unit) connectionsRepository.defaultConnectionIsValidated.value = false + runCurrent() // THEN the forced validation remains for exactly 2 more seconds from now // 1.500s from second event advanceTimeBy(1500) - assertThat(latestConnectionFailed).isFalse() + assertThat(latest).isFalse() // 2.001s from the second event advanceTimeBy(501) - assertThat(latestConnectionFailed).isTrue() - - job.cancel() + assertThat(latest).isTrue() } @Test fun isForceHidden_repoHasMobileHidden_true() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.isForceHidden) connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.MOBILE)) assertThat(latest).isTrue() - - job.cancel() } @Test fun isForceHidden_repoDoesNotHaveMobileHidden_false() = testScope.runTest { - var latest: Boolean? = null - val job = underTest.isForceHidden.onEach { latest = it }.launchIn(this) + val latest by collectLastValue(underTest.isForceHidden) connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI)) assertThat(latest).isFalse() - - job.cancel() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt index cfa734a14811..ab10bc4a4acc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt @@ -47,7 +47,7 @@ import platform.test.runner.parameterized.Parameters @SmallTest @OptIn(ExperimentalCoroutinesApi::class) @RunWith(ParameterizedAndroidJunit4::class) -class KeyguardStatusBarViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { +class KeyguardStatusBarViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository } @@ -66,7 +66,7 @@ class KeyguardStatusBarViewModelTest(flags: FlagsParameterization?) : SysuiTestC } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt index d1d259826ac2..15032dc182d6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MockExecutorHandlerTest.kt @@ -141,6 +141,88 @@ class MockExecutorHandlerTest : SysuiTestCase() { assertEquals(3, runnable.mRunCount) } + @Test + fun testRemoveCallback_postDelayed() { + val clock = FakeSystemClock() + val fakeExecutor = FakeExecutor(clock) + val handler = mockExecutorHandler(fakeExecutor) + val runnable = RunnableImpl() + + handler.postDelayed(runnable, 50) + handler.postDelayed(runnable, 150) + fakeExecutor.advanceClockToNext() + fakeExecutor.runAllReady() + + assertEquals(1, runnable.mRunCount) + assertEquals(1, fakeExecutor.numPending()) + + handler.removeCallbacks(runnable) + assertEquals(0, fakeExecutor.numPending()) + + assertEquals(1, runnable.mRunCount) + } + + @Test + fun testRemoveCallback_postAtTime() { + val clock = FakeSystemClock() + val fakeExecutor = FakeExecutor(clock) + val handler = mockExecutorHandler(fakeExecutor) + val runnable = RunnableImpl() + assertEquals(10000, clock.uptimeMillis()) + + handler.postAtTime(runnable, 10050) + handler.postAtTime(runnable, 10150) + fakeExecutor.advanceClockToNext() + fakeExecutor.runAllReady() + + assertEquals(1, runnable.mRunCount) + assertEquals(1, fakeExecutor.numPending()) + + handler.removeCallbacks(runnable) + assertEquals(0, fakeExecutor.numPending()) + + assertEquals(1, runnable.mRunCount) + } + + @Test + fun testRemoveCallback_mixed_allRemoved() { + val clock = FakeSystemClock() + val fakeExecutor = FakeExecutor(clock) + val handler = mockExecutorHandler(fakeExecutor) + val runnable = RunnableImpl() + assertEquals(10000, clock.uptimeMillis()) + + handler.postAtTime(runnable, 10050) + handler.postDelayed(runnable, 150) + + handler.removeCallbacks(runnable) + assertEquals(0, fakeExecutor.numPending()) + + fakeExecutor.advanceClockToLast() + fakeExecutor.runAllReady() + assertEquals(0, runnable.mRunCount) + } + + @Test + fun testRemoveCallback_differentRunnables_onlyMatchingRemoved() { + val clock = FakeSystemClock() + val fakeExecutor = FakeExecutor(clock) + val handler = mockExecutorHandler(fakeExecutor) + val runnable1 = RunnableImpl() + val runnable2 = RunnableImpl() + + handler.postDelayed(runnable1, 50) + handler.postDelayed(runnable2, 150) + + handler.removeCallbacks(runnable1) + assertEquals(1, fakeExecutor.numPending()) + + fakeExecutor.advanceClockToLast() + fakeExecutor.runAllReady() + assertEquals(0, runnable1.mRunCount) + assertEquals(1, runnable2.mRunCount) + } + /** * Verifies that `Handler.removeMessages`, which doesn't make sense with executor backing, * causes an error in the test (rather than failing silently like most mocks). diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index 3b468aa011ec..9864439266b2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -65,7 +65,6 @@ import android.widget.ImageButton; import android.widget.SeekBar; import androidx.test.core.view.MotionEventBuilder; -import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import com.android.internal.jank.InteractionJankMonitor; @@ -273,54 +272,30 @@ public class VolumeDialogImplTest extends SysuiTestCase { @Test @DisableFlags(FLAG_HAPTIC_VOLUME_SLIDER) - public void testVolumeChange_noSliderHaptics_doesNotDeliverOnProgressChangedHaptics() { - final State shellState = createShellState(); - VolumeDialogController.StreamState musicStreamState = - shellState.states.get(AudioSystem.STREAM_MUSIC); - - mDialog.show(SHOW_REASON_UNKNOWN); - mTestableLooper.processMessages(1); //Only the SHOW message - mDialog.removeDismissMessages(); // Temporarily remove the rescheduled DISMISS - - // Change the volume two times - musicStreamState.level += 10; - mDialog.onStateChangedH(shellState); - musicStreamState.level += 10; - mDialog.onStateChangedH(shellState); + public void addSliderHaptics_withHapticsDisabled_doesNotDeliverOnProgressChangedHaptics() { + // GIVEN that the slider haptics flag is disabled and we try to add haptics to volume rows + mDialog.addSliderHapticsToRows(); - // expected: the type of the latest progress haptics for the stream should be DISABLED - int type = mDialog.progressHapticsForStream(AudioSystem.STREAM_MUSIC); - assertEquals(VolumeDialogImpl.PROGRESS_HAPTICS_DISABLED, type); + // WHEN haptics try to be delivered to a volume stream + boolean canDeliverHaptics = + mDialog.canDeliverProgressHapticsToStream(AudioSystem.STREAM_MUSIC, true, 50); - mDialog.dismiss(DISMISS_REASON_UNKNOWN); // Dismiss - mTestableLooper.processAllMessages(); + // THEN the result is that haptics are not successfully delivered + assertFalse(canDeliverHaptics); } - @Test @FlakyTest(bugId = 329099861) + @Test @EnableFlags(FLAG_HAPTIC_VOLUME_SLIDER) - public void testVolumeChange_withSliderHaptics_deliversOnProgressChangedHapticsEagerly() { - // create haptic plugins on the rows with the flag enabled + public void addSliderHaptics_withHapticsEnabled_canDeliverOnProgressChangedHaptics() { + // GIVEN that the slider haptics flag is enabled and we try to add haptics to volume rows mDialog.addSliderHapticsToRows(); - final State shellState = createShellState(); - VolumeDialogController.StreamState musicStreamState = - shellState.states.get(AudioSystem.STREAM_MUSIC); - - mDialog.show(SHOW_REASON_UNKNOWN); - mTestableLooper.processMessages(1); //Only the SHOW message - mDialog.removeDismissMessages(); // Temporarily remove the rescheduled DISMISS - // Change the volume two times - musicStreamState.level += 10; - mDialog.onStateChangedH(shellState); - musicStreamState.level += 10; - mDialog.onStateChangedH(shellState); + // WHEN haptics try to be delivered to a volume stream + boolean canDeliverHaptics = + mDialog.canDeliverProgressHapticsToStream(AudioSystem.STREAM_MUSIC, true, 50); - // expected: the type of the latest progress haptics for the stream should be EAGER - int type = mDialog.progressHapticsForStream(AudioSystem.STREAM_MUSIC); - assertEquals(VolumeDialogImpl.PROGRESS_HAPTICS_EAGER, type); - - mDialog.dismiss(DISMISS_REASON_UNKNOWN); // Dismiss - mTestableLooper.processAllMessages(); + // THEN the result is that haptics are successfully delivered + assertTrue(canDeliverHaptics); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 56e5e293c799..aac36405e9b6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -138,7 +138,6 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt index 4085b1b5b5c5..923b63656914 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityRepository.kt @@ -25,8 +25,9 @@ import kotlinx.coroutines.flow.MutableStateFlow @SysUISingleton class FakeAccessibilityRepository( override val isTouchExplorationEnabled: MutableStateFlow<Boolean>, + override val isEnabled: MutableStateFlow<Boolean>, ) : AccessibilityRepository { - @Inject constructor() : this(MutableStateFlow(false)) + @Inject constructor() : this(MutableStateFlow(false), MutableStateFlow(false)) } @Module diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt index 66c9afba0cd6..b23767e9a6e1 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/ActivityTransitionAnimatorKosmos.kt @@ -17,5 +17,13 @@ package com.android.systemui.animation import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase -val Kosmos.activityTransitionAnimator by Kosmos.Fixture { ActivityTransitionAnimator() } +val Kosmos.activityTransitionAnimator by + Kosmos.Fixture { + ActivityTransitionAnimator( + // The main thread is checked in a bunch of places inside the different transitions + // animators, so we have to pass the real main executor here. + mainExecutor = testCase.context.mainExecutor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt index 77cb1670bc42..5a092f38f929 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/DialogTransitionAnimatorKosmos.kt @@ -19,7 +19,13 @@ package com.android.systemui.animation import com.android.systemui.jank.interactionJankMonitor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.testCase val Kosmos.dialogTransitionAnimator by Fixture { - fakeDialogTransitionAnimator(interactionJankMonitor = interactionJankMonitor) + fakeDialogTransitionAnimator( + // The main thread is checked in a bunch of places inside the different transitions + // animators, so we have to pass the real main executor here. + mainExecutor = testCase.context.mainExecutor, + interactionJankMonitor = interactionJankMonitor, + ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt index 48b72d0a4cf1..17093291e8b0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt @@ -15,17 +15,20 @@ package com.android.systemui.animation import com.android.internal.jank.InteractionJankMonitor -import com.android.systemui.jank.interactionJankMonitor +import com.android.systemui.dagger.qualifiers.Main +import java.util.concurrent.Executor /** A [DialogTransitionAnimator] to be used in tests. */ @JvmOverloads fun fakeDialogTransitionAnimator( + @Main mainExecutor: Executor, isUnlocked: Boolean = true, isShowingAlternateAuthOnUnlock: Boolean = false, isPredictiveBackQsDialogAnim: Boolean = false, interactionJankMonitor: InteractionJankMonitor, ): DialogTransitionAnimator { return DialogTransitionAnimator( + mainExecutor = mainExecutor, callback = FakeCallback( isUnlocked = isUnlocked, @@ -36,7 +39,7 @@ fun fakeDialogTransitionAnimator( object : AnimationFeatureFlags { override val isPredictiveBackQsDialogAnim = isPredictiveBackQsDialogAnim }, - transitionAnimator = fakeTransitionAnimator(), + transitionAnimator = fakeTransitionAnimator(mainExecutor), isForTesting = true, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt index bc7ec3f3b6d0..d07875f24beb 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeTransitionAnimator.kt @@ -15,10 +15,12 @@ package com.android.systemui.animation import com.android.app.animation.Interpolators +import com.android.systemui.dagger.qualifiers.Main +import java.util.concurrent.Executor /** A [TransitionAnimator] to be used in tests. */ -fun fakeTransitionAnimator(): TransitionAnimator { - return TransitionAnimator(TEST_TIMINGS, TEST_INTERPOLATORS) +fun fakeTransitionAnimator(@Main mainExecutor: Executor): TransitionAnimator { + return TransitionAnimator(mainExecutor, TEST_TIMINGS, TEST_INTERPOLATORS) } /** diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt index 0975687b4744..e37bdc15c0ac 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt @@ -1,8 +1,6 @@ package com.android.systemui.biometrics.data.repository -import android.hardware.biometrics.Flags import android.hardware.biometrics.PromptInfo -import com.android.systemui.biometrics.Utils import com.android.systemui.biometrics.shared.model.PromptKind import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -22,15 +20,12 @@ class FakePromptRepository : PromptRepository { private var _challenge = MutableStateFlow<Long?>(null) override val challenge = _challenge.asStateFlow() - private val _kind = MutableStateFlow<PromptKind>(PromptKind.Biometric()) - override val kind = _kind.asStateFlow() + private val _promptKind = MutableStateFlow<PromptKind>(PromptKind.None) + override val promptKind = _promptKind.asStateFlow() private val _isConfirmationRequired = MutableStateFlow(false) override val isConfirmationRequired = _isConfirmationRequired.asStateFlow() - private val _showBpWithoutIconForCredential = MutableStateFlow(false) - override val showBpWithoutIconForCredential = _showBpWithoutIconForCredential.asStateFlow() - private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null) override val opPackageName = _opPackageName.asStateFlow() @@ -61,7 +56,7 @@ class FakePromptRepository : PromptRepository { _promptInfo.value = promptInfo _userId.value = userId _challenge.value = gatekeeperChallenge - _kind.value = kind + _promptKind.value = kind _isConfirmationRequired.value = promptInfo.isConfirmationRequested || forceConfirmation _opPackageName.value = opPackageName } @@ -70,22 +65,11 @@ class FakePromptRepository : PromptRepository { _promptInfo.value = null _userId.value = null _challenge.value = null - _kind.value = PromptKind.Biometric() + _promptKind.value = PromptKind.None + _opPackageName.value = null _isConfirmationRequired.value = false } - override fun setShouldShowBpWithoutIconForCredential(promptInfo: PromptInfo) { - val hasCredentialViewShown = kind.value !is PromptKind.Biometric - val showBpForCredential = - Flags.customBiometricPrompt() && - com.android.systemui.Flags.constraintBp() && - !Utils.isBiometricAllowed(promptInfo) && - Utils.isDeviceCredentialAllowed(promptInfo) && - promptInfo.contentView != null && - !promptInfo.isContentViewMoreOptionsButtonUsed - _showBpWithoutIconForCredential.value = showBpForCredential && !hasCredentialViewShown - } - fun setIsShowing(showing: Boolean) { _isShowing.value = showing } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt index 2ae6f542ac3e..4999a5a29a7a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelKosmos.kt @@ -17,6 +17,8 @@ package com.android.systemui.biometrics.ui.viewmodel import android.content.applicationContext +import com.android.app.activityTaskManager +import com.android.launcher3.icons.IconProvider import com.android.systemui.biometrics.domain.interactor.biometricStatusInteractor import com.android.systemui.biometrics.domain.interactor.displayStateInteractor import com.android.systemui.biometrics.domain.interactor.promptSelectorInteractor @@ -32,6 +34,8 @@ val Kosmos.promptViewModel by Fixture { context = applicationContext, udfpsOverlayInteractor = udfpsOverlayInteractor, biometricStatusInteractor = biometricStatusInteractor, - udfpsUtils = udfpsUtils + udfpsUtils = udfpsUtils, + iconProvider = IconProvider(applicationContext), + activityTaskManager = activityTaskManager, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt index 3a61bf625804..9b3482b8ce42 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt @@ -18,8 +18,12 @@ package com.android.systemui.common.data.repository import android.os.UserHandle import com.android.systemui.common.shared.model.PackageChangeModel +import com.android.systemui.common.shared.model.PackageInstallSession import com.android.systemui.util.time.SystemClock +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.filter class FakePackageChangeRepository(private val systemClock: SystemClock) : PackageChangeRepository { @@ -31,6 +35,15 @@ class FakePackageChangeRepository(private val systemClock: SystemClock) : Packag user == UserHandle.ALL || user == UserHandle.getUserHandleForUid(it.packageUid) } + private val _packageInstallSessions = MutableStateFlow<List<PackageInstallSession>>(emptyList()) + + override val packageInstallSessionsForPrimaryUser: Flow<List<PackageInstallSession>> = + _packageInstallSessions.asStateFlow() + + fun setInstallSessions(sessions: List<PackageInstallSession>) { + _packageInstallSessions.value = sessions + } + suspend fun notifyChange(model: PackageChangeModel) { _packageChanged.emit(model) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt index 329c0f1ab5b4..f7ce367ebb18 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt @@ -51,6 +51,7 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) : override fun abortRestoreWidgets() {} private fun onConfigured(id: Int, providerInfo: AppWidgetProviderInfo, priority: Int) { - _communalWidgets.value += listOf(CommunalWidgetContentModel(id, providerInfo, priority)) + _communalWidgets.value += + listOf(CommunalWidgetContentModel.Available(id, providerInfo, priority)) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt index 90a93f4e7544..a6b40df8e81b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt @@ -24,6 +24,7 @@ import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBl import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection 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.kosmos.Kosmos import com.android.systemui.util.mockito.mock @@ -37,6 +38,7 @@ val Kosmos.keyguardClockSection: ClockSection by context = applicationContext, smartspaceViewModel = keyguardSmartspaceViewModel, blueprintInteractor = { keyguardBlueprintInteractor }, + rootViewModel = keyguardRootViewModel, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt index 408157b7614f..3e69e875cd0d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt @@ -17,7 +17,10 @@ package com.android.systemui.keyguard.data.repository import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testDispatcher var Kosmos.keyguardTransitionRepository: KeyguardTransitionRepository by Kosmos.Fixture { fakeKeyguardTransitionRepository } var Kosmos.fakeKeyguardTransitionRepository by Kosmos.Fixture { FakeKeyguardTransitionRepository() } +var Kosmos.realKeyguardTransitionRepository: KeyguardTransitionRepository by + Kosmos.Fixture { KeyguardTransitionRepositoryImpl(testDispatcher) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.kt new file mode 100644 index 000000000000..7d0e8b1b4b53 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/LockscreenSceneTransitionRepository.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.keyguard.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.lockscreenSceneTransitionRepository by + Kosmos.Fixture { LockscreenSceneTransitionRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt index 29167d64d1f1..b38acc8a46dc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.kosmos.Kosmos +import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.statusbar.notification.domain.interactor.notificationLaunchAnimationInteractor val Kosmos.windowManagerLockscreenVisibilityInteractor by @@ -29,5 +30,6 @@ val Kosmos.windowManagerLockscreenVisibilityInteractor by fromBouncerInteractor = fromPrimaryBouncerTransitionInteractor, fromAlternateBouncerInteractor = fromAlternateBouncerTransitionInteractor, notificationLaunchAnimationInteractor = notificationLaunchAnimationInteractor, + sceneInteractor = sceneInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt new file mode 100644 index 000000000000..3c1f7b1b2394 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor.scenetransition + +import com.android.systemui.keyguard.data.repository.lockscreenSceneTransitionRepository +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.scene.domain.interactor.sceneInteractor + +var Kosmos.lockscreenSceneTransitionInteractor by + Kosmos.Fixture { + LockscreenSceneTransitionInteractor( + transitionInteractor = keyguardTransitionInteractor, + applicationScope = applicationCoroutineScope, + sceneInteractor = sceneInteractor, + repository = lockscreenSceneTransitionRepository, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt index 58b0ff8513f5..67fa857a1ecd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel +import com.android.systemui.accessibility.domain.interactor.accessibilityInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntrySourceInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor @@ -49,6 +50,7 @@ val Kosmos.deviceEntryIconViewModel by Fixture { keyguardViewController = { statusBarKeyguardViewManager }, deviceEntryInteractor = deviceEntryInteractor, deviceEntrySourceInteractor = deviceEntrySourceInteractor, + accessibilityInteractor = accessibilityInteractor, scope = testScope.backgroundScope, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt index 3762497656c6..ec56327c1101 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt @@ -20,6 +20,7 @@ import com.android.internal.logging.uiEventLogger import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.jank.interactionJankMonitor import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor @@ -33,6 +34,7 @@ var Kosmos.statusBarStateController: SysuiStatusBarStateController by uiEventLogger, { interactionJankMonitor }, mock(), + { keyguardTransitionInteractor }, { shadeInteractor }, { deviceUnlockedInteractor }, { sceneInteractor }, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt index 3f9112238822..d72630dc2f03 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt @@ -32,6 +32,7 @@ import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testDispatcher import com.android.systemui.plugins.activityStarter import com.android.systemui.plugins.qs.QSFactory +import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractorImpl import com.android.systemui.qs.footer.foregroundServicesRepository import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel @@ -48,7 +49,7 @@ val Kosmos.qsEventLogger: QsEventLoggerFake by Fixture { QsEventLoggerFake(uiEventLoggerFake, instanceIdSequenceFake) } -var Kosmos.qsTileFactory by Fixture<QSFactory>() +var Kosmos.qsTileFactory by Fixture<QSFactory> { FakeQSFactory(::tileCreator) } val Kosmos.fgsManagerController by Fixture { FakeFgsManagerController() } @@ -98,3 +99,7 @@ val Kosmos.footerActionsViewModelFactory by Fixture { showPowerButton = true, ) } + +private fun tileCreator(spec: String): QSTile { + return FakeQSTile(0).apply { tileSpec = spec } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt index cff59807e00f..744942c0053b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt @@ -16,11 +16,11 @@ package com.android.systemui.qs.external -import android.os.Binder import android.os.IBinder +import android.os.IInterface import android.service.quicksettings.IQSTileService -class FakeIQSTileService : IQSTileService { +class FakeIQSTileService : IQSTileService.Stub() { var isTileAdded: Boolean = false private set @@ -31,9 +31,11 @@ class FakeIQSTileService : IQSTileService { get() = mutableClicks private val mutableClicks: MutableList<IBinder?> = mutableListOf() - private val binder = Binder() + override fun queryLocalInterface(descriptor: String): IInterface { + return this + } - override fun asBinder(): IBinder = binder + override fun asBinder(): IBinder = this override fun onTileAdded() { isTileAdded = true diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt new file mode 100644 index 000000000000..a0fc76b3d7de --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.external + +import android.app.activityManager +import android.content.applicationContext +import android.os.fakeExecutorHandler +import com.android.systemui.broadcast.broadcastDispatcher +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade +import com.android.systemui.util.mockito.mock + +val Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by + Kosmos.Fixture { + TileLifecycleManager.Factory { intent, userHandle -> + TileLifecycleManager( + fakeExecutorHandler, + applicationContext, + tileServices, + packageManagerAdapterFacade.packageManagerAdapter, + broadcastDispatcher, + intent, + userHandle, + activityManager, + mock(), + fakeExecutor, + ) + } + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServicesKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServicesKosmos.kt new file mode 100644 index 000000000000..3f129dac2600 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServicesKosmos.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.external + +import android.content.applicationContext +import android.os.fakeExecutorHandler +import com.android.systemui.broadcast.broadcastDispatcher +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository +import com.android.systemui.qs.pipeline.domain.interactor.panelInteractor +import com.android.systemui.settings.userTracker +import com.android.systemui.statusbar.commandQueue +import com.android.systemui.statusbar.phone.ui.StatusBarIconController +import com.android.systemui.statusbar.policy.keyguardStateController +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever + +val Kosmos.tileServices: TileServices by + Kosmos.Fixture { + val qsHost: QSHost = mock { whenever(context).thenReturn(applicationContext) } + TileServices( + qsHost, + { fakeExecutorHandler }, + broadcastDispatcher, + userTracker, + keyguardStateController, + commandQueue, + mock<StatusBarIconController>(), + panelInteractor, + tileLifecycleManagerFactory, + customTileAddedRepository, + fakeExecutor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt index 36c2c2b6eb23..9a6730e07666 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt @@ -18,13 +18,9 @@ package com.android.systemui.qs.external import android.content.ComponentName import com.android.systemui.kosmos.Kosmos -import com.android.systemui.util.mockito.mock var Kosmos.componentName: ComponentName by Kosmos.Fixture() -/** Returns mocks */ -var Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by Kosmos.Fixture { mock {} } - val Kosmos.iQSTileService: FakeIQSTileService by Kosmos.Fixture { FakeIQSTileService() } val Kosmos.tileServiceManagerFacade: FakeTileServiceManagerFacade by Kosmos.Fixture { FakeTileServiceManagerFacade(iQSTileService) } @@ -34,4 +30,3 @@ val Kosmos.tileServiceManager: TileServiceManager by val Kosmos.tileServicesFacade: FakeTileServicesFacade by Kosmos.Fixture { (FakeTileServicesFacade(tileServiceManager)) } -val Kosmos.tileServices: TileServices by Kosmos.Fixture { tileServicesFacade.tileServices } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt index f846d5712497..4acedaa9044d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/GridLayoutTypeRepositoryKosmos.kt @@ -18,4 +18,5 @@ package com.android.systemui.qs.panels.data.repository import com.android.systemui.kosmos.Kosmos -val Kosmos.gridLayoutTypeRepository by Kosmos.Fixture { GridLayoutTypeRepository() } +var Kosmos.gridLayoutTypeRepository: GridLayoutTypeRepository by + Kosmos.Fixture { GridLayoutTypeRepositoryImpl() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt index 685e77223fd5..e40152aa588f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconTilesRepositoryKosmos.kt @@ -18,4 +18,4 @@ package com.android.systemui.qs.panels.data.repository import com.android.systemui.kosmos.Kosmos -val Kosmos.iconTilesRepository by Kosmos.Fixture { IconTilesRepositoryImpl() } +var Kosmos.iconTilesRepository: IconTilesRepository by Kosmos.Fixture { IconTilesRepositoryImpl() } diff --git a/core/java/android/app/StatusBarManager.aidl b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepositoryKosmos.kt index 687678c4ea8d..d8af3fa98d7b 100644 --- a/core/java/android/app/StatusBarManager.aidl +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepositoryKosmos.kt @@ -1,11 +1,11 @@ -/** - * Copyright (c) 2024, The Android Open Source Project +/* + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,6 +14,8 @@ * limitations under the License. */ -package android.app; +package com.android.systemui.qs.panels.data.repository -parcelable StatusBarManager.DisableInfo;
\ No newline at end of file +import com.android.systemui.kosmos.Kosmos + +val Kosmos.infiniteGridSizeRepository by Kosmos.Fixture { InfiniteGridSizeRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt new file mode 100644 index 000000000000..edbc4c1cd65e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorKosmos.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.log.core.FakeLogBuffer +import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor + +val Kosmos.gridConsistencyInteractor by + Kosmos.Fixture { + GridConsistencyInteractor( + gridLayoutTypeInteractor, + currentTilesInteractor, + gridConsistencyInteractorsMap, + noopGridConsistencyInteractor, + FakeLogBuffer.Factory.create(), + applicationCoroutineScope, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt index c9516429553b..34e99d3a9a3c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractorKosmos.kt @@ -27,3 +27,6 @@ val Kosmos.gridLayoutTypeInteractor by val Kosmos.gridLayoutMap: Map<GridLayoutType, GridLayout> by Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridLayout)) } + +var Kosmos.gridConsistencyInteractorsMap: Map<GridLayoutType, GridTypeConsistencyInteractor> by + Kosmos.Fixture { mapOf(Pair(InfiniteGridLayoutType, infiniteGridConsistencyInteractor)) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt new file mode 100644 index 000000000000..7f387d7c706d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.domain.interactor + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.infiniteGridConsistencyInteractor by + Kosmos.Fixture { + InfiniteGridConsistencyInteractor(iconTilesInteractor, infiniteGridSizeInteractor) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt index 1893c302c332..34b266a54f41 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt @@ -19,4 +19,5 @@ package com.android.systemui.qs.panels.domain.interactor import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout -val Kosmos.infiniteGridLayout by Kosmos.Fixture { InfiniteGridLayout(iconTilesInteractor) } +val Kosmos.infiniteGridLayout by + Kosmos.Fixture { InfiniteGridLayout(iconTilesInteractor, infiniteGridSizeInteractor) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractorKosmos.kt new file mode 100644 index 000000000000..6e1197737630 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridSizeInteractorKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.panels.data.repository.infiniteGridSizeRepository + +val Kosmos.infiniteGridSizeInteractor by + Kosmos.Fixture { InfiniteGridSizeInteractor(infiniteGridSizeRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt new file mode 100644 index 000000000000..e3beff727ae3 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/NoopGridConsistencyInteractorKosmos.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.domain.interactor + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.noopGridConsistencyInteractor by Kosmos.Fixture { NoopGridConsistencyInteractor() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt index 5fd876222852..9481fcac97d6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelKosmos.kt @@ -20,8 +20,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.qs.panels.domain.interactor.gridLayoutMap import com.android.systemui.qs.panels.domain.interactor.gridLayoutTypeInteractor -import com.android.systemui.qs.panels.domain.interactor.iconTilesInteractor -import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout +import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor val Kosmos.tileGridViewModel by @@ -30,7 +29,7 @@ val Kosmos.tileGridViewModel by gridLayoutTypeInteractor, gridLayoutMap, currentTilesInteractor, - InfiniteGridLayout(iconTilesInteractor), + infiniteGridLayout, applicationCoroutineScope, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorKosmos.kt new file mode 100644 index 000000000000..d10780b6b817 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorKosmos.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.qs.pipeline.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.shade.shadeController + +val Kosmos.panelInteractor by Kosmos.Fixture { PanelInteractorImpl(shadeController) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt index 5c4b39081143..419e7810b604 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt @@ -16,23 +16,63 @@ package com.android.systemui.qs.tiles.di +import android.os.UserHandle import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.instanceIdSequenceFake +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel import com.android.systemui.qs.tiles.viewmodel.qSTileConfigProvider import com.android.systemui.qs.tiles.viewmodel.qsTileViewModelAdaperFactory +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever import javax.inject.Provider -import org.mockito.Mockito +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow var Kosmos.newFactoryTileMap by Kosmos.Fixture { emptyMap<String, Provider<QSTileViewModel>>() } +val Kosmos.customTileViewModelFactory: QSTileViewModelFactory.Component by + Kosmos.Fixture { + mock { + whenever(create(any())).thenAnswer { invocation -> + val tileSpec = invocation.getArgument<TileSpec>(0) + val config = + QSTileConfig( + tileSpec, + QSTileUIConfig.Empty, + instanceIdSequenceFake.newInstanceId(), + ) + object : QSTileViewModel { + override val state: SharedFlow<QSTileState> = + MutableStateFlow(QSTileState.build({ null }, tileSpec.spec) {}) + override val config: QSTileConfig = config + override val isAvailable: StateFlow<Boolean> = MutableStateFlow(true) + + override fun onUserChanged(user: UserHandle) {} + + override fun forceUpdate() {} + + override fun onActionPerformed(userAction: QSTileUserAction) {} + + override fun destroy() {} + } + } + } + } + val Kosmos.newQSTileFactory by Kosmos.Fixture { NewQSTileFactory( qSTileConfigProvider, qsTileViewModelAdaperFactory, newFactoryTileMap, - mock(Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS)), - mock(Mockito.withSettings().defaultAnswer(Mockito.RETURNS_MOCKS)), + customTileViewModelFactory, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt index 561e2540d465..42437d5a5b81 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt @@ -23,6 +23,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.activityStarter import com.android.systemui.qs.external.FakeCustomTileStatePersister import com.android.systemui.qs.external.tileServices +import com.android.systemui.qs.external.tileServicesFacade import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler import com.android.systemui.qs.tiles.base.logging.QSTileLogger @@ -38,10 +39,10 @@ import com.android.systemui.qs.tiles.viewmodel.QSTileConfigTestBuilder import com.android.systemui.user.data.repository.userRepository import com.android.systemui.util.mockito.mock -var Kosmos.tileSpec: TileSpec.CustomTileSpec by Kosmos.Fixture() +var Kosmos.customTileSpec: TileSpec.CustomTileSpec by Kosmos.Fixture() var Kosmos.customTileQsTileConfig: QSTileConfig by - Kosmos.Fixture { QSTileConfigTestBuilder.build { tileSpec = this@Fixture.tileSpec } } + Kosmos.Fixture { QSTileConfigTestBuilder.build { tileSpec = this@Fixture.customTileSpec } } val Kosmos.qsTileLogger: QSTileLogger by Kosmos.Fixture { mock {} } val Kosmos.customTileStatePersister: FakeCustomTileStatePersister by @@ -50,7 +51,7 @@ val Kosmos.customTileStatePersister: FakeCustomTileStatePersister by val Kosmos.customTileInteractor: CustomTileInteractor by Kosmos.Fixture { CustomTileInteractor( - tileSpec, + customTileSpec, customTileDefaultsRepository, customTileRepository, testScope.backgroundScope, @@ -61,7 +62,7 @@ val Kosmos.customTileInteractor: CustomTileInteractor by val Kosmos.customTileRepository: FakeCustomTileRepository by Kosmos.Fixture { FakeCustomTileRepository( - tileSpec, + customTileSpec, customTileStatePersister, packageManagerAdapterFacade, testScope.testScheduler, @@ -75,18 +76,18 @@ val Kosmos.customTilePackagesUpdatesRepository: FakeCustomTilePackageUpdatesRepo Kosmos.Fixture { FakeCustomTilePackageUpdatesRepository() } val Kosmos.packageManagerAdapterFacade: FakePackageManagerAdapterFacade by - Kosmos.Fixture { FakePackageManagerAdapterFacade(tileSpec.componentName) } + Kosmos.Fixture { FakePackageManagerAdapterFacade(customTileSpec.componentName) } val Kosmos.customTileServiceInteractor: CustomTileServiceInteractor by Kosmos.Fixture { CustomTileServiceInteractor( - tileSpec, + customTileSpec, activityStarter, { customTileUserActionInteractor }, customTileInteractor, userRepository, qsTileLogger, - tileServices, + tileServicesFacade.tileServices, testScope.backgroundScope, ) } @@ -95,7 +96,7 @@ val Kosmos.customTileUserActionInteractor: CustomTileUserActionInteractor by Kosmos.Fixture { CustomTileUserActionInteractor( testCase.context, - tileSpec, + customTileSpec, qsTileLogger, mock {}, mock {}, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt index 634d121a556c..fa8d36366415 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles.impl.custom.data.repository import android.content.ComponentName +import android.content.pm.PackageInfo import android.content.pm.ServiceInfo import android.os.Bundle import com.android.systemui.qs.external.PackageManagerAdapter @@ -24,6 +25,7 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever +import org.mockito.ArgumentMatchers.anyInt /** * Facade for [PackageManagerAdapter] to provide a fake-like behaviour. You can create this class @@ -45,19 +47,33 @@ class FakePackageManagerAdapterFacade( init { whenever(packageManagerAdapter.getServiceInfo(eq(componentName), any())).thenAnswer { - ServiceInfo().apply { - metaData = - Bundle().apply { - putBoolean( - android.service.quicksettings.TileService.META_DATA_TOGGLEABLE_TILE, - isToggleable - ) - putBoolean( - android.service.quicksettings.TileService.META_DATA_ACTIVE_TILE, - isActive - ) - } - } + createServiceInfo() + } + whenever( + packageManagerAdapter.getPackageInfoAsUser( + eq(componentName.packageName), + anyInt(), + anyInt() + ) + ) + .thenAnswer { PackageInfo().apply { packageName = componentName.packageName } } + whenever(packageManagerAdapter.getServiceInfo(eq(componentName), anyInt(), anyInt())) + .thenAnswer { createServiceInfo() } + } + + private fun createServiceInfo(): ServiceInfo { + return ServiceInfo().apply { + metaData = + Bundle().apply { + putBoolean( + android.service.quicksettings.TileService.META_DATA_TOGGLEABLE_TILE, + isToggleable + ) + putBoolean( + android.service.quicksettings.TileService.META_DATA_ACTIVE_TILE, + isActive + ) + } } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/night/NightDisplayTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/night/NightDisplayTileKosmos.kt new file mode 100644 index 000000000000..5c21ab6e7fa8 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/night/NightDisplayTileKosmos.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.night + +import com.android.systemui.accessibility.qs.QSAccessibilityModule +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.qsEventLogger + +val Kosmos.qsNightDisplayTileConfig by + Kosmos.Fixture { QSAccessibilityModule.provideNightDisplayTileConfig(qsEventLogger) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt index 38ede44efef6..ea02d0c7ac9a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt @@ -76,6 +76,16 @@ class ShadeTestUtil constructor(val delegate: ShadeTestUtilDelegate) { delegate.assertFlagValid() delegate.programmaticCollapseShade() } + + fun setQsFullscreen(qsFullscreen: Boolean) { + delegate.assertFlagValid() + delegate.setQsFullscreen(qsFullscreen) + } + + fun setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer: Boolean) { + delegate.assertFlagValid() + delegate.setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer) + } } /** Sets up shade state for tests for a specific value of the scene container flag. */ @@ -103,6 +113,10 @@ interface ShadeTestUtilDelegate { /** Sets the shade to half collapsed with no touch input. */ fun programmaticCollapseShade() + + fun setQsFullscreen(qsFullscreen: Boolean) + + fun setLegacyExpandedOrAwaitingInputTransfer(legacyExpandedOrAwaitingInputTransfer: Boolean) } /** Sets up shade state for tests when the scene container flag is disabled. */ @@ -146,6 +160,14 @@ class ShadeTestUtilLegacyImpl(val testScope: TestScope, val shadeRepository: Fak shadeRepository.setLegacyShadeExpansion(.5f) testScope.runCurrent() } + + override fun setQsFullscreen(qsFullscreen: Boolean) { + shadeRepository.legacyQsFullscreen.value = true + } + + override fun setLegacyExpandedOrAwaitingInputTransfer(expanded: Boolean) { + shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(expanded) + } } /** Sets up shade state for tests when the scene container flag is enabled. */ @@ -183,6 +205,16 @@ class ShadeTestUtilSceneImpl(val testScope: TestScope, val sceneInteractor: Scen setTransitionProgress(Scenes.Shade, Scenes.Lockscreen, .5f, false) } + override fun setQsFullscreen(qsFullscreen: Boolean) { + setQsExpansion(1f) + } + + override fun setLegacyExpandedOrAwaitingInputTransfer( + legacyExpandedOrAwaitingInputTransfer: Boolean + ) { + setShadeExpansion(.1f) + } + override fun setLockscreenShadeExpansion(lockscreenShadeExpansion: Float) { if (lockscreenShadeExpansion == 0f) { setIdleScene(Scenes.Lockscreen) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryKosmos.kt new file mode 100644 index 000000000000..3bb9580e69fa --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryKosmos.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone + +import android.content.applicationContext +import com.android.systemui.animation.dialogTransitionAnimator +import com.android.systemui.broadcast.broadcastDispatcher +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.model.sysUiState + +val Kosmos.systemUIDialogFactory by + Kosmos.Fixture { + SystemUIDialogFactory( + applicationContext, + systemUIDialogManager, + sysUiState, + broadcastDispatcher, + dialogTransitionAnimator, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/MockExecutorHandler.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/MockExecutorHandler.kt index 184d4b53f2f8..92f9248b2935 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/MockExecutorHandler.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/MockExecutorHandler.kt @@ -17,6 +17,8 @@ package com.android.systemui.util.concurrency import android.os.Handler +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.Executor import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyLong @@ -29,9 +31,14 @@ import org.mockito.stubbing.Answer * Wrap an [Executor] in a mock [Handler] that execute when [Handler.post] is called, and throws an * exception otherwise. This is useful when a class requires a Handler only because Handlers are * used by ContentObserver, and no other methods are used. + * + * If the [executor] is a [DelayableExecutor], it also supports: + * * [Handler.postDelayed] with a Runnable parameter + * * [Handler.postAtTime] with a RunnableParameter */ fun mockExecutorHandler(executor: Executor): Handler { val handlerMock = Mockito.mock(Handler::class.java, RuntimeExceptionAnswer()) + val cancellations = ConcurrentHashMap<Runnable, MutableList<Cancellation>>() doAnswer { invocation: InvocationOnMock -> executor.execute(invocation.getArgument(0)) true @@ -42,7 +49,19 @@ fun mockExecutorHandler(executor: Executor): Handler { doAnswer { invocation: InvocationOnMock -> val runnable = invocation.getArgument<Runnable>(0) val uptimeMillis = invocation.getArgument<Long>(1) - executor.executeAtTime(runnable, uptimeMillis) + val token = Any() + val canceller = + executor.executeAtTime( + { + cancellations.get(runnable)?.removeIf { it.token == token } + cancellations.remove(runnable, emptyList()) + runnable.run() + }, + uptimeMillis + ) + cancellations + .getOrPut(runnable) { CopyOnWriteArrayList() } + .add(Cancellation(token, canceller)) true } .`when`(handlerMock) @@ -50,15 +69,36 @@ fun mockExecutorHandler(executor: Executor): Handler { doAnswer { invocation: InvocationOnMock -> val runnable = invocation.getArgument<Runnable>(0) val delayInMillis = invocation.getArgument<Long>(1) - executor.executeDelayed(runnable, delayInMillis) + val token = Any() + val canceller = + executor.executeDelayed( + { + cancellations.get(runnable)?.removeIf { it.token == token } + cancellations.remove(runnable, emptyList()) + runnable.run() + }, + delayInMillis + ) + cancellations + .getOrPut(runnable) { CopyOnWriteArrayList() } + .add(Cancellation(token, canceller)) true } .`when`(handlerMock) .postDelayed(any(), anyLong()) + doAnswer { invocation: InvocationOnMock -> + val runnable = invocation.getArgument<Runnable>(0) + cancellations.remove(runnable)?.forEach(Runnable::run) + Unit + } + .`when`(handlerMock) + .removeCallbacks(any()) } return handlerMock } +private class Cancellation(val token: Any, canceller: Runnable) : Runnable by canceller + private class RuntimeExceptionAnswer : Answer<Any> { override fun answer(invocation: InvocationOnMock): Any { throw RuntimeException(invocation.method.name + " is not stubbed") diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt index d74355894581..59bbff171725 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt @@ -19,9 +19,8 @@ package com.android.systemui.volume import android.content.packageManager import android.content.pm.ApplicationInfo import android.os.Handler -import android.testing.TestableLooper +import android.os.looper import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.media.mediaOutputDialogManager import com.android.systemui.util.mockito.any @@ -54,7 +53,7 @@ val Kosmos.mediaOutputInteractor by testScope.backgroundScope, testScope.testScheduler, mediaControllerRepository, - Handler(TestableLooper.get(testCase).looper), + Handler(looper), ) } @@ -62,7 +61,7 @@ val Kosmos.mediaDeviceSessionInteractor by Kosmos.Fixture { MediaDeviceSessionInteractor( testScope.testScheduler, - Handler(TestableLooper.get(testCase).looper), + Handler(looper), mediaControllerRepository, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorKosmos.kt new file mode 100644 index 000000000000..d1d873ee1082 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorKosmos.kt @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.domain.interactor + +import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.notification.domain.interactor.notificationsSoundPolicyInteractor +import com.android.systemui.volume.data.repository.audioRepository + +val Kosmos.audioVolumeInteractor by + Kosmos.Fixture { + AudioVolumeInteractor( + audioRepository, + notificationsSoundPolicyInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt deleted file mode 100644 index 146f1093fe69..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/VolumePanelKosmos.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.volume.panel - -import android.content.res.mainResources -import com.android.systemui.broadcast.broadcastDispatcher -import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.testScope -import com.android.systemui.statusbar.policy.fakeConfigurationController -import com.android.systemui.util.mockito.mock -import com.android.systemui.volume.panel.dagger.factory.KosmosVolumePanelComponentFactory -import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria -import com.android.systemui.volume.panel.domain.TestComponentAvailabilityCriteria -import com.android.systemui.volume.panel.domain.VolumePanelStartable -import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor -import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractorImpl -import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey -import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent -import com.android.systemui.volume.panel.ui.composable.ComponentsFactory -import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager -import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel -import javax.inject.Provider - -val Kosmos.mockVolumePanelUiComponent: VolumePanelUiComponent by Kosmos.Fixture { mock {} } -val Kosmos.mockVolumePanelUiComponentProvider: Provider<VolumePanelUiComponent> by - Kosmos.Fixture { Provider { mockVolumePanelUiComponent } } -var Kosmos.componentByKey: Map<VolumePanelComponentKey, Provider<VolumePanelUiComponent>> by - Kosmos.Fixture { emptyMap() } -val Kosmos.componentsFactory: ComponentsFactory by - Kosmos.Fixture { ComponentsFactory(componentByKey) } - -var Kosmos.componentsLayoutManager: ComponentsLayoutManager by Kosmos.Fixture() -var Kosmos.enabledComponents: Collection<VolumePanelComponentKey> by - Kosmos.Fixture { componentByKey.keys } -var Kosmos.volumePanelStartables: Set<VolumePanelStartable> by - Kosmos.Fixture { emptySet<VolumePanelStartable>() } -val Kosmos.unavailableCriteria: Provider<ComponentAvailabilityCriteria> by - Kosmos.Fixture { Provider { TestComponentAvailabilityCriteria(false) } } -val Kosmos.availableCriteria: Provider<ComponentAvailabilityCriteria> by - Kosmos.Fixture { Provider { TestComponentAvailabilityCriteria(true) } } -var Kosmos.defaultCriteria: Provider<ComponentAvailabilityCriteria> by - Kosmos.Fixture { availableCriteria } -var Kosmos.criteriaByKey: Map<VolumePanelComponentKey, Provider<ComponentAvailabilityCriteria>> by - Kosmos.Fixture { emptyMap() } -var Kosmos.componentsInteractor: ComponentsInteractor by - Kosmos.Fixture { - ComponentsInteractorImpl( - enabledComponents, - defaultCriteria, - testScope.backgroundScope, - criteriaByKey, - ) - } - -var Kosmos.volumePanelViewModel: VolumePanelViewModel by - Kosmos.Fixture { - VolumePanelViewModel( - mainResources, - testScope.backgroundScope, - KosmosVolumePanelComponentFactory(this), - fakeConfigurationController, - broadcastDispatcher, - ) - } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponentKosmos.kt new file mode 100644 index 000000000000..2ea27c7e32de --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/bottombar/ui/BottomBarComponentKosmos.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.component.bottombar.ui + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.volume.panel.component.bottombar.ui.viewmodel.bottomBarViewModel +import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria +import com.android.systemui.volume.panel.domain.availableCriteria + +var Kosmos.bottomBarComponent by Kosmos.Fixture { BottomBarComponent(bottomBarViewModel) } +var Kosmos.bottomBarAvailabilityCriteria: ComponentAvailabilityCriteria by + Kosmos.Fixture { availableCriteria } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelKosmos.kt new file mode 100644 index 000000000000..128ede1f8108 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/bottombar/ui/viewmodel/BottomBarViewModelKosmos.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.component.bottombar.ui.viewmodel + +import com.android.internal.logging.uiEventLogger +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.plugins.activityStarter +import com.android.systemui.volume.panel.ui.viewmodel.volumePanelViewModel + +var Kosmos.bottomBarViewModel: BottomBarViewModel by + Kosmos.Fixture { + BottomBarViewModel( + activityStarter, + volumePanelViewModel, + uiEventLogger, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModuleKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModuleKosmos.kt new file mode 100644 index 000000000000..0c814c566d63 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/MediaOutputModuleKosmos.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.component.mediaoutput + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.volume.panel.component.mediaoutput.ui.composable.MediaOutputComponent +import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.mediaOutputViewModel +import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria +import com.android.systemui.volume.panel.domain.availableCriteria + +var Kosmos.mediaOutputComponent: MediaOutputComponent by + Kosmos.Fixture { MediaOutputComponent(mediaOutputViewModel) } +var Kosmos.mediaOutputAvailabilityCriteria: ComponentAvailabilityCriteria by + Kosmos.Fixture { availableCriteria } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelKosmos.kt new file mode 100644 index 000000000000..6d4576efd34c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelKosmos.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.volume.panel.component.mediaoutput.ui.viewmodel + +import android.content.applicationContext +import com.android.internal.logging.uiEventLogger +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.volume.domain.interactor.audioModeInteractor +import com.android.systemui.volume.domain.interactor.audioOutputInteractor +import com.android.systemui.volume.mediaDeviceSessionInteractor +import com.android.systemui.volume.mediaOutputActionsInteractor +import com.android.systemui.volume.mediaOutputInteractor + +var Kosmos.mediaOutputViewModel by + Kosmos.Fixture { + MediaOutputViewModel( + applicationContext, + testScope.backgroundScope, + mediaOutputActionsInteractor, + mediaDeviceSessionInteractor, + audioOutputInteractor, + audioModeInteractor, + mediaOutputInteractor, + uiEventLogger, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/VolumeModuleKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/VolumeModuleKosmos.kt new file mode 100644 index 000000000000..a17e7454ed65 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/VolumeModuleKosmos.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.component.volume + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.volume.panel.component.volume.ui.composable.VolumeSlidersComponent +import com.android.systemui.volume.panel.component.volume.ui.viewmodel.audioVolumeComponentViewModel +import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria +import com.android.systemui.volume.panel.domain.availableCriteria + +var Kosmos.volumeSlidersComponent: VolumeSlidersComponent by + Kosmos.Fixture { VolumeSlidersComponent(audioVolumeComponentViewModel) } +var Kosmos.volumeSlidersAvailabilityCriteria: ComponentAvailabilityCriteria by + Kosmos.Fixture { availableCriteria } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt new file mode 100644 index 000000000000..a0a39d14dcf6 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.component.volume.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.volume.data.repository.audioRepository +import com.android.systemui.volume.mediaOutputInteractor + +val Kosmos.audioSlidersInteractor by + Kosmos.Fixture { + AudioSlidersInteractor( + testScope.backgroundScope, + mediaOutputInteractor, + audioRepository, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt new file mode 100644 index 000000000000..b2b19de654b6 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel + +import android.content.applicationContext +import com.android.internal.logging.uiEventLogger +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.volume.domain.interactor.audioVolumeInteractor +import kotlinx.coroutines.CoroutineScope + +val Kosmos.audioStreamSliderViewModelFactory by + Kosmos.Fixture { + object : AudioStreamSliderViewModel.Factory { + + override fun create( + audioStream: AudioStreamSliderViewModel.FactoryAudioStreamWrapper, + coroutineScope: CoroutineScope, + ): AudioStreamSliderViewModel { + return AudioStreamSliderViewModel( + audioStream, + coroutineScope, + applicationContext, + audioVolumeInteractor, + uiEventLogger, + ) + } + } + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt new file mode 100644 index 000000000000..f0cb2cd904ca --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModelKosmos.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel + +import android.content.applicationContext +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.volume.mediaDeviceSessionInteractor +import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession +import kotlinx.coroutines.CoroutineScope + +val Kosmos.castVolumeSliderViewModelFactory by + Kosmos.Fixture { + object : CastVolumeSliderViewModel.Factory { + override fun create( + session: MediaDeviceSession, + coroutineScope: CoroutineScope + ): CastVolumeSliderViewModel { + return CastVolumeSliderViewModel( + session, + coroutineScope, + applicationContext, + mediaDeviceSessionInteractor, + ) + } + } + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/VolumeSlidersViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/VolumeSlidersViewModelKosmos.kt new file mode 100644 index 000000000000..45a291e0e401 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/VolumeSlidersViewModelKosmos.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.component.volume.ui.viewmodel + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.volume.mediaDeviceSessionInteractor +import com.android.systemui.volume.mediaOutputInteractor +import com.android.systemui.volume.panel.component.volume.domain.interactor.audioSlidersInteractor +import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.audioStreamSliderViewModelFactory +import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.castVolumeSliderViewModelFactory + +val Kosmos.audioVolumeComponentViewModel by + Kosmos.Fixture { + AudioVolumeComponentViewModel( + testScope.backgroundScope, + mediaOutputInteractor, + mediaDeviceSessionInteractor, + audioStreamSliderViewModelFactory, + castVolumeSliderViewModelFactory, + audioSlidersInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt index 1e895b5ed4de..587a7ea589bc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/KosmosVolumePanelComponentFactory.kt @@ -18,16 +18,16 @@ package com.android.systemui.volume.panel.dagger.factory import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope -import com.android.systemui.volume.panel.componentsFactory -import com.android.systemui.volume.panel.componentsInteractor -import com.android.systemui.volume.panel.componentsLayoutManager import com.android.systemui.volume.panel.dagger.VolumePanelComponent import com.android.systemui.volume.panel.domain.VolumePanelStartable import com.android.systemui.volume.panel.domain.interactor.ComponentsInteractor +import com.android.systemui.volume.panel.domain.interactor.componentsInteractor import com.android.systemui.volume.panel.ui.composable.ComponentsFactory +import com.android.systemui.volume.panel.ui.composable.componentsFactory import com.android.systemui.volume.panel.ui.layout.ComponentsLayoutManager +import com.android.systemui.volume.panel.ui.layout.componentsLayoutManager import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel -import com.android.systemui.volume.panel.volumePanelStartables +import com.android.systemui.volume.panel.ui.viewmodel.volumePanelStartables import kotlinx.coroutines.CoroutineScope class KosmosVolumePanelComponentFactory(private val kosmos: Kosmos) : VolumePanelComponentFactory { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/VolumePanelComponentFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/VolumePanelComponentFactoryKosmos.kt new file mode 100644 index 000000000000..b0b06f1935f9 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/dagger/factory/VolumePanelComponentFactoryKosmos.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.dagger.factory + +import com.android.systemui.kosmos.Kosmos + +var Kosmos.volumePanelComponentFactory by Kosmos.Fixture { KosmosVolumePanelComponentFactory(this) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/ComponentAvailabilityCriteriaKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/ComponentAvailabilityCriteriaKosmos.kt new file mode 100644 index 000000000000..e0547eed75e9 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/ComponentAvailabilityCriteriaKosmos.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.domain + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.unavailableCriteria: ComponentAvailabilityCriteria by + Kosmos.Fixture { TestComponentAvailabilityCriteria(false) } +val Kosmos.availableCriteria: ComponentAvailabilityCriteria by + Kosmos.Fixture { AlwaysAvailableCriteria() } +var Kosmos.defaultCriteria: ComponentAvailabilityCriteria by Kosmos.Fixture { availableCriteria } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt new file mode 100644 index 000000000000..8862942aa083 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/domain/interactor/ComponentsInteractorKosmos.kt @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.volume.panel.component.bottombar.ui.bottomBarAvailabilityCriteria +import com.android.systemui.volume.panel.component.mediaoutput.mediaOutputAvailabilityCriteria +import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents +import com.android.systemui.volume.panel.component.volume.volumeSlidersAvailabilityCriteria +import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria +import com.android.systemui.volume.panel.domain.defaultCriteria +import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey +import com.android.systemui.volume.panel.ui.composable.enabledComponents +import javax.inject.Provider + +var Kosmos.criteriaByKey: Map<VolumePanelComponentKey, Provider<ComponentAvailabilityCriteria>> by + Kosmos.Fixture { emptyMap() } +var Kosmos.prodCriteriaByKey: + Map<VolumePanelComponentKey, Provider<ComponentAvailabilityCriteria>> by + Kosmos.Fixture { + mapOf( + VolumePanelComponents.MEDIA_OUTPUT to Provider { mediaOutputAvailabilityCriteria }, + VolumePanelComponents.VOLUME_SLIDERS to Provider { volumeSlidersAvailabilityCriteria }, + VolumePanelComponents.BOTTOM_BAR to Provider { bottomBarAvailabilityCriteria }, + ) + } + +var Kosmos.componentsInteractor: ComponentsInteractor by + Kosmos.Fixture { + ComponentsInteractorImpl( + enabledComponents, + { defaultCriteria }, + testScope.backgroundScope, + criteriaByKey, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/shared/model/VolumePanelUiComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/shared/model/VolumePanelUiComponentKosmos.kt new file mode 100644 index 000000000000..afe8e622eee6 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/shared/model/VolumePanelUiComponentKosmos.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.shared.model + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock +import javax.inject.Provider + +val Kosmos.mockVolumePanelUiComponent: VolumePanelUiComponent by Kosmos.Fixture { mock {} } +val Kosmos.mockVolumePanelUiComponentProvider: Provider<VolumePanelUiComponent> by + Kosmos.Fixture { Provider { mock {} } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryKosmos.kt new file mode 100644 index 000000000000..bacf22c0fef6 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/composable/ComponentsFactoryKosmos.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.ui.composable + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.volume.panel.component.bottombar.ui.bottomBarComponent +import com.android.systemui.volume.panel.component.mediaoutput.mediaOutputComponent +import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents +import com.android.systemui.volume.panel.component.volume.volumeSlidersComponent +import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey +import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent +import javax.inject.Provider + +var Kosmos.componentByKey: Map<VolumePanelComponentKey, Provider<VolumePanelUiComponent>> by + Kosmos.Fixture { emptyMap() } +var Kosmos.prodComponentByKey: Map<VolumePanelComponentKey, Provider<VolumePanelUiComponent>> by + Kosmos.Fixture { + mapOf( + VolumePanelComponents.BOTTOM_BAR to Provider { bottomBarComponent }, + VolumePanelComponents.MEDIA_OUTPUT to Provider { mediaOutputComponent }, + VolumePanelComponents.VOLUME_SLIDERS to Provider { volumeSlidersComponent }, + ) + } +var Kosmos.enabledComponents: Collection<VolumePanelComponentKey> by + Kosmos.Fixture { componentByKey.keys } + +val Kosmos.componentsFactory: ComponentsFactory by + Kosmos.Fixture { ComponentsFactory(componentByKey) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayoutManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayoutManagerKosmos.kt new file mode 100644 index 000000000000..e0480e7cf986 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/layout/ComponentsLayoutManagerKosmos.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.ui.layout + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents +import com.android.systemui.volume.panel.shared.model.VolumePanelComponentKey + +var Kosmos.volumePanelBottomBarComponentKey: VolumePanelComponentKey by + Kosmos.Fixture { VolumePanelComponents.BOTTOM_BAR } +var Kosmos.volumePanelHeaderComponentKeys: Collection<VolumePanelComponentKey> by + Kosmos.Fixture { listOf(VolumePanelComponents.MEDIA_OUTPUT) } +var Kosmos.volumePanelFooterComponentKeys: Collection<VolumePanelComponentKey> by + Kosmos.Fixture { + listOf( + VolumePanelComponents.ANC, + VolumePanelComponents.SPATIAL_AUDIO, + VolumePanelComponents.CAPTIONING, + ) + } + +var Kosmos.componentsLayoutManager: ComponentsLayoutManager by + Kosmos.Fixture { + DefaultComponentsLayoutManager( + bottomBar = volumePanelBottomBarComponentKey, + headerComponents = volumePanelHeaderComponentKeys, + footerComponents = volumePanelFooterComponentKeys, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt new file mode 100644 index 000000000000..a60658830be4 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/ui/viewmodel/VolumePanelViewModelKosmos.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.ui.viewmodel + +import android.content.applicationContext +import com.android.systemui.broadcast.broadcastDispatcher +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.policy.configurationController +import com.android.systemui.volume.panel.dagger.factory.volumePanelComponentFactory +import com.android.systemui.volume.panel.domain.VolumePanelStartable + +var Kosmos.volumePanelStartables: Set<VolumePanelStartable> by Kosmos.Fixture { emptySet() } + +var Kosmos.volumePanelViewModel: VolumePanelViewModel by + Kosmos.Fixture { volumePanelViewModelFactory.create(testScope.backgroundScope) } + +val Kosmos.volumePanelViewModelFactory: VolumePanelViewModel.Factory by + Kosmos.Fixture { + VolumePanelViewModel.Factory( + applicationContext, + volumePanelComponentFactory, + configurationController, + broadcastDispatcher, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/ui/navigation/VolumeNavigatorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/ui/navigation/VolumeNavigatorKosmos.kt new file mode 100644 index 000000000000..63b3f237a953 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/ui/navigation/VolumeNavigatorKosmos.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.ui.navigation + +import com.android.internal.logging.uiEventLoggerFake +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.mainCoroutineContext +import com.android.systemui.kosmos.testScope +import com.android.systemui.plugins.activityStarter +import com.android.systemui.statusbar.phone.systemUIDialogFactory +import com.android.systemui.util.mockito.mock +import com.android.systemui.volume.VolumePanelFactory +import com.android.systemui.volume.panel.ui.viewmodel.volumePanelViewModelFactory + +val Kosmos.volumeNavigator by + Kosmos.Fixture { + VolumeNavigator( + testScope, + mainCoroutineContext, + mock<VolumePanelFactory> { /* Unsupported and unused */}, + activityStarter, + volumePanelViewModelFactory, + systemUIDialogFactory, + uiEventLoggerFake, + ) + } diff --git a/ravenwood/scripts/convert-androidtest.py b/ravenwood/scripts/convert-androidtest.py new file mode 100755 index 000000000000..61ec54b667c5 --- /dev/null +++ b/ravenwood/scripts/convert-androidtest.py @@ -0,0 +1,184 @@ +#!/usr/bin/python3 +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This script converts a legacy test class (using AndroidTestCase, TestCase or +# InstrumentationTestCase to a modern style test class, in a best-effort manner. +# +# Usage: +# convert-androidtest.py TARGET-FILE [TARGET-FILE ...] +# +# Caveats: +# - It adds all the extra imports, even if they're not needed. +# - It won't sort imports. +# - It also always adds getContext() and getTestContext(). +# + +import sys +import fileinput +import re +import subprocess + +# Print message on console +def log(msg): + print(msg, file=sys.stderr) + + +# Matches `extends AndroidTestCase` (or another similar base class) +re_extends = re.compile( + r''' \b extends \s+ (AndroidTestCase|TestCase|InstrumentationTestCase) \s* ''', + re.S + re.X) + + +# Look into given files and return the files that have `re_extends`. +def find_target_files(files): + ret = [] + + for file in files: + try: + with open(file, 'r') as f: + data = f.read() + + if re_extends.search(data): + ret.append(file) + + except FileNotFoundError as e: + log(f'Failed to open file {file}: {e}') + + return ret + + +def main(args): + files = args + + # Find the files that should be processed. + files = find_target_files(files) + + if len(files) == 0: + log("No target files found.") + return 0 + + # Process the files. + with fileinput.input(files=(files), inplace = True, backup = '.bak') as f: + import_seen = False + carry_over = '' + class_body_started = False + class_seen = False + + def on_file_start(): + nonlocal import_seen, carry_over, class_body_started, class_seen + import_seen = False + carry_over = '' + class_body_started = False + class_seen = False + + for line in f: + if (fileinput.filelineno() == 1): + log(f"Processing: {fileinput.filename()}") + on_file_start() + + line = line.rstrip('\n') + + # Carry over a certain line to the next line. + if re.search(r'''@Override\b''', line): + carry_over = carry_over + line + '\n' + continue + + if carry_over: + line = carry_over + line + carry_over = '' + + + # Remove the base class from the class definition. + line = re_extends.sub('', line) + + # Add a @RunWith. + if not class_seen and re.search(r'''\b class \b''', line, re.X): + class_seen = True + print("@RunWith(AndroidJUnit4.class)") + + + # Inject extra imports. + if not import_seen and re.search(r'''^import\b''', line): + import_seen = True + print("""\ +import android.content.Context; +import androidx.test.platform.app.InstrumentationRegistry; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertSame; +import static junit.framework.TestCase.assertNotSame; +import static junit.framework.TestCase.assertTrue; +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertNull; +import static junit.framework.TestCase.assertNotNull; +import static junit.framework.TestCase.fail; + +import org.junit.After; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.junit.Test; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +""") + + # Add @Test to the test methods. + if re.search(r'''^ \s* public \s* void \s* test''', line, re.X): + print(" @Test") + + # Convert setUp/tearDown to @Before/@After. + if re.search(r''' ^\s+ ( \@Override \s+ ) ? (public|protected) \s+ void \s+ (setUp|tearDown) ''', + line, re.X): + if re.search('setUp', line): + print(' @Before') + else: + print(' @After') + + line = re.sub(r''' \s* \@Override \s* \n ''', '', line, 0, re.X) + line = re.sub(r'''protected''', 'public', line, 0, re.X) + + # Remove the super setUp / tearDown call. + if re.search(r''' \b super \. (setUp|tearDown) \b ''', line, re.X): + continue + + # Convert mContext to getContext(). + line = re.sub(r'''\b mContext \b ''', 'getContext()', line, 0, re.X) + + # Print the processed line. + print(line) + + # Add getContext() / getTestContext() at the beginning of the class. + if not class_body_started and re.search(r'''\{''', line): + class_body_started = True + print("""\ + private Context getContext() { + return InstrumentationRegistry.getInstrumentation().getTargetContext(); + } + + private Context getTestContext() { + return InstrumentationRegistry.getInstrumentation().getContext(); + } +""") + + + # Run diff + for file in files: + subprocess.call(["diff", "-u", "--color=auto", f"{file}.bak", file]) + + log(f'{len(files)} file(s) converted.') + + return 0 + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index e830523cba60..06a02978701b 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -83,6 +83,7 @@ import android.graphics.Point; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; @@ -172,6 +173,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private static final String TAG = "AppWidgetServiceImpl"; private static final boolean DEBUG = false; + private static final boolean DEBUG_NULL_PROVIDER_INFO = Build.IS_DEBUGGABLE; private static final String OLD_KEYGUARD_HOST_PACKAGE = "android"; private static final String NEW_KEYGUARD_HOST_PACKAGE = "com.android.keyguard"; @@ -736,7 +738,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.work_widget_mask_view); - ApplicationInfo appInfo = provider.info.providerInfo.applicationInfo; + final ActivityInfo activityInfo = provider.info.providerInfo; + final ApplicationInfo appInfo = activityInfo != null ? activityInfo.applicationInfo : null; + final String packageName = appInfo != null + ? appInfo.packageName : provider.id.componentName.getPackageName(); final int appUserId = provider.getUserId(); boolean showBadge = false; @@ -750,7 +755,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } else if (provider.maskedBySuspendedPackage) { showBadge = mUserManager.hasBadge(appUserId); final UserPackage suspendingPackage = mPackageManagerInternal.getSuspendingPackage( - appInfo.packageName, appUserId); + packageName, appUserId); // TODO(b/281839596): don't rely on platform always meaning suspended by admin. if (suspendingPackage != null && PLATFORM_PACKAGE_NAME.equals(suspendingPackage.packageName)) { @@ -759,11 +764,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } else { final SuspendDialogInfo dialogInfo = mPackageManagerInternal.getSuspendedDialogInfo( - appInfo.packageName, suspendingPackage, appUserId); + packageName, suspendingPackage, appUserId); // onUnsuspend is null because we don't want to start any activity on // unsuspending from a suspended widget. onClickIntent = SuspendedAppActivity.createSuspendedAppInterceptIntent( - appInfo.packageName, suspendingPackage, dialogInfo, null, null, + packageName, suspendingPackage, dialogInfo, null, null, appUserId); } } else if (provider.maskedByLockedProfile) { @@ -778,7 +783,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku showBadge = mUserManager.hasBadge(appUserId); } - Icon icon = appInfo.icon != 0 + Icon icon = (appInfo != null && appInfo.icon != 0) ? Icon.createWithResource(appInfo.packageName, appInfo.icon) : Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon); views.setImageViewIcon(R.id.work_widget_app_icon, icon); @@ -2955,6 +2960,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku AppWidgetProviderInfo info = new AppWidgetProviderInfo(); info.provider = providerId.componentName; info.providerInfo = ri.activityInfo; + if (DEBUG_NULL_PROVIDER_INFO) { + Objects.requireNonNull(ri.activityInfo); + } return info; } return null; @@ -2989,6 +2997,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku AppWidgetProviderInfo info = new AppWidgetProviderInfo(); info.provider = providerId.componentName; info.providerInfo = activityInfo; + if (DEBUG_NULL_PROVIDER_INFO) { + Objects.requireNonNull(activityInfo); + } final Resources resources; final long identity = Binder.clearCallingIdentity(); @@ -3564,6 +3575,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku AppWidgetProviderInfo info = new AppWidgetProviderInfo(); info.provider = providerId.componentName; info.providerInfo = providerInfo; + if (DEBUG_NULL_PROVIDER_INFO) { + Objects.requireNonNull(providerInfo); + } provider = new Provider(); provider.setPartialInfoLocked(info); @@ -3580,6 +3594,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku if (info != null) { info.provider = providerId.componentName; info.providerInfo = providerInfo; + if (DEBUG_NULL_PROVIDER_INFO) { + Objects.requireNonNull(providerInfo); + } provider.setInfoLocked(info); } } @@ -5714,7 +5731,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // so we tear it down in anticipation of it (possibly) being // reconstructed due to the restore host.widgets.remove(widget); - provider.widgets.remove(widget); + if (provider != null) { + provider.widgets.remove(widget); + } // Check if we need to destroy any services (if no other app widgets are // referencing the same service) decrementAppWidgetServiceRefCount(widget); diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 763879e5379a..6fc05b72da9b 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -1998,6 +1998,19 @@ public final class AutofillManagerService } @Override + public void setViewAutofilled(int sessionId, @NonNull AutofillId id, int userId) { + synchronized (mLock) { + final AutofillManagerServiceImpl service = + peekServiceForUserWithLocalBinderIdentityLocked(userId); + if (service != null) { + service.setViewAutofilled(sessionId, getCallingUid(), id); + } else if (sVerbose) { + Slog.v(TAG, "setAutofillFailure(): no service for " + userId); + } + } + } + + @Override public void finishSession(int sessionId, int userId, @AutofillCommitReason int commitReason) { synchronized (mLock) { diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index 92acce24e64e..588266fba47a 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -466,6 +466,7 @@ final class AutofillManagerServiceImpl @GuardedBy("mLock") void setAutofillFailureLocked(int sessionId, int uid, @NonNull List<AutofillId> ids) { if (!isEnabledLocked()) { + Slog.wtf(TAG, "Service not enabled"); return; } final Session session = mSessions.get(sessionId); @@ -477,8 +478,23 @@ final class AutofillManagerServiceImpl } @GuardedBy("mLock") + void setViewAutofilled(int sessionId, int uid, @NonNull AutofillId id) { + if (!isEnabledLocked()) { + Slog.wtf(TAG, "Service not enabled"); + return; + } + final Session session = mSessions.get(sessionId); + if (session == null || uid != session.uid) { + Slog.v(TAG, "setViewAutofilled(): no session for " + sessionId + "(" + uid + ")"); + return; + } + session.setViewAutofilled(id); + } + + @GuardedBy("mLock") void finishSessionLocked(int sessionId, int uid, @AutofillCommitReason int commitReason) { if (!isEnabledLocked()) { + Slog.wtf(TAG, "Service not enabled"); return; } diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java index 9c84b123a435..f289115159b8 100644 --- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java +++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java @@ -65,6 +65,7 @@ import android.content.pm.PackageManager; import android.provider.Settings; import android.service.autofill.Dataset; import android.text.TextUtils; +import android.util.ArraySet; import android.util.Slog; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; @@ -548,9 +549,10 @@ public final class PresentationStatsEventLogger { /** * Set views_fillable_total_count as long as mEventInternal presents. */ - public void maybeSetViewFillableCounts(int totalFillableCount) { + public void maybeSetViewFillablesAndCount(List<AutofillId> autofillIds) { mEventInternal.ifPresent(event -> { - event.mViewFillableTotalCount = totalFillableCount; + event.mAutofillIdsAttemptedAutofill = new ArraySet<>(autofillIds); + event.mViewFillableTotalCount = event.mAutofillIdsAttemptedAutofill.size(); }); } @@ -564,6 +566,41 @@ public final class PresentationStatsEventLogger { }); } + /** Sets focused_autofill_id using view id */ + public void maybeSetFocusedId(AutofillId id) { + maybeSetFocusedId(id.getViewId()); + } + + /** Sets focused_autofill_id as long as mEventInternal is present */ + public void maybeSetFocusedId(int id) { + mEventInternal.ifPresent(event -> { + event.mFocusedId = id; + }); + } + /** + * Set views_filled_failure_count using failure count as long as mEventInternal + * presents. + */ + public void maybeAddSuccessId(AutofillId autofillId) { + mEventInternal.ifPresent(event -> { + ArraySet<AutofillId> autofillIds = event.mAutofillIdsAttemptedAutofill; + if (autofillIds == null) { + Slog.w(TAG, "Attempted autofill ids is null, but received autofillId:" + autofillId + + " successfully filled"); + event.mViewFilledButUnexpectedCount++; + } else if (autofillIds.contains(autofillId)) { + if (sVerbose) { + Slog.v(TAG, "Logging autofill for id:" + autofillId); + event.mViewFillSuccessCount++; + } + } else { + Slog.w(TAG, "Successfully filled autofillId:" + autofillId + + " not found in list of attempted autofill ids: " + autofillIds); + event.mViewFilledButUnexpectedCount++; + } + }); + } + public void logAndEndEvent() { if (!mEventInternal.isPresent()) { Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same " @@ -608,7 +645,10 @@ public final class PresentationStatsEventLogger { + " mIsCredentialRequest=" + event.mIsCredentialRequest + " mWebviewRequestedCredential=" + event.mWebviewRequestedCredential + " mViewFillableTotalCount=" + event.mViewFillableTotalCount - + " mViewFillFailureCount=" + event.mViewFillFailureCount); + + " mViewFillFailureCount=" + event.mViewFillFailureCount + + " mFocusedId=" + event.mFocusedId + + " mViewFillSuccessCount=" + event.mViewFillSuccessCount + + " mViewFilledButUnexpectedCount=" + event.mViewFilledButUnexpectedCount); } // TODO(b/234185326): Distinguish empty responses from other no presentation reasons. @@ -651,7 +691,10 @@ public final class PresentationStatsEventLogger { event.mIsCredentialRequest, event.mWebviewRequestedCredential, event.mViewFillableTotalCount, - event.mViewFillFailureCount); + event.mViewFillFailureCount, + event.mFocusedId, + event.mViewFillSuccessCount, + event.mViewFilledButUnexpectedCount); mEventInternal = Optional.empty(); } @@ -689,7 +732,14 @@ public final class PresentationStatsEventLogger { boolean mWebviewRequestedCredential = false; int mViewFillableTotalCount = -1; int mViewFillFailureCount = -1; + int mFocusedId = -1; + + // Default value for success count is set to 0 explicitly. Setting it to -1 for + // uninitialized doesn't help much, as this would be non-zero only if callback is received. + int mViewFillSuccessCount = 0; + int mViewFilledButUnexpectedCount = 0; + ArraySet<AutofillId> mAutofillIdsAttemptedAutofill; PresentationStatsEventInternal() {} } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 8b13c4b762d5..07b16c53ffe8 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -4669,6 +4669,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mFieldClassificationIdSnapshot); mPresentationStatsEventLogger.maybeSetAvailableCount( response.getDatasets(), mCurrentViewId); + mPresentationStatsEventLogger.maybeSetFocusedId(mCurrentViewId); } @GuardedBy("mLock") @@ -5381,7 +5382,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } mPresentationStatsEventLogger.maybeSetViewFillFailureCounts(ids.size()); - mPresentationStatsEventLogger.logAndEndEvent(); + } + + /** + * Sets the state of views that failed to autofill. + */ + @GuardedBy("mLock") + void setViewAutofilled(@NonNull AutofillId id) { + if (sVerbose) { + Slog.v(TAG, "View autofilled: " + id); + } + if (id.getSessionId() == AutofillId.NO_SESSION) { + id.setSessionId(this.id); + } + mPresentationStatsEventLogger.maybeAddSuccessId(id); } @GuardedBy("mLock") @@ -6589,7 +6603,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sVerbose) { Slog.v(TAG, "Total views to be autofilled: " + ids.size()); } - mPresentationStatsEventLogger.maybeSetViewFillableCounts(ids.size()); + mPresentationStatsEventLogger.maybeSetViewFillablesAndCount(ids); if (sDebug) Slog.d(TAG, "autoFillApp(): the buck is on the app: " + dataset); mClient.autofill(id, ids, values, hideHighlight); if (dataset.getId() != null) { diff --git a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java index 70443f9153d1..38a412fa063d 100644 --- a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java +++ b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java @@ -118,7 +118,7 @@ final class RemoteInlineSuggestionViewConnector { final InputMethodManagerInternal inputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class); if (!inputMethodManagerInternal.transferTouchFocusToImeWindow(sourceInputToken, - displayId)) { + displayId, mUserId)) { Slog.e(TAG, "Cannot transfer touch focus from suggestion to IME"); mOnErrorCallback.run(); } diff --git a/services/backup/java/com/android/server/backup/transport/TransportConnection.java b/services/backup/java/com/android/server/backup/transport/TransportConnection.java index 1009787bebe5..67ebb3e6a1ef 100644 --- a/services/backup/java/com/android/server/backup/transport/TransportConnection.java +++ b/services/backup/java/com/android/server/backup/transport/TransportConnection.java @@ -658,11 +658,13 @@ public class TransportConnection { * This class is a proxy to TransportClient methods that doesn't hold a strong reference to the * TransportClient, allowing it to be GC'ed. If the reference was lost it logs a message. */ - private static class TransportConnectionMonitor implements ServiceConnection { + @VisibleForTesting + static class TransportConnectionMonitor implements ServiceConnection { private final Context mContext; private final WeakReference<TransportConnection> mTransportClientRef; - private TransportConnectionMonitor(Context context, + @VisibleForTesting + TransportConnectionMonitor(Context context, TransportConnection transportConnection) { mContext = context; mTransportClientRef = new WeakReference<>(transportConnection); @@ -704,7 +706,13 @@ public class TransportConnection { /** @see TransportConnection#finalize() */ private void referenceLost(String caller) { - mContext.unbindService(this); + try { + mContext.unbindService(this); + } catch (IllegalArgumentException e) { + TransportUtils.log(Priority.WARN, TAG, + caller + " called but unbindService failed: " + e.getMessage()); + return; + } TransportUtils.log( Priority.INFO, TAG, diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java index 9069689ee5eb..026d29c9f821 100644 --- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java +++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java @@ -135,7 +135,7 @@ public class SystemDataTransferProcessor { */ public PendingIntent buildPermissionTransferUserConsentIntent(String packageName, @UserIdInt int userId, int associationId) { - if (PackageUtils.isPackageAllowlisted(mContext, mPackageManager, packageName)) { + if (PackageUtils.isPermSyncAutoEnabled(mContext, mPackageManager, packageName)) { Slog.i(LOG_TAG, "User consent Intent should be skipped. Returning null."); // Auto enable perm sync for the allowlisted packages, but don't override user decision PermissionSyncRequest request = getPermissionSyncRequest(associationId); diff --git a/services/companion/java/com/android/server/companion/utils/PackageUtils.java b/services/companion/java/com/android/server/companion/utils/PackageUtils.java index 254d28b1b2c2..94ab9dddd1f6 100644 --- a/services/companion/java/com/android/server/companion/utils/PackageUtils.java +++ b/services/companion/java/com/android/server/companion/utils/PackageUtils.java @@ -21,6 +21,11 @@ import static android.content.pm.PackageManager.GET_CONFIGURATIONS; import static android.content.pm.PackageManager.GET_PERMISSIONS; import static android.os.Binder.getCallingUid; +import static com.android.internal.R.array.config_companionDeviceCerts; +import static com.android.internal.R.array.config_companionDevicePackages; +import static com.android.internal.R.array.config_companionPermSyncEnabledCerts; +import static com.android.internal.R.array.config_companionPermSyncEnabledPackages; + import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; @@ -185,15 +190,30 @@ public final class PackageUtils { */ public static boolean isPackageAllowlisted(Context context, PackageManagerInternal packageManagerInternal, @NonNull String packageName) { - final String[] allowlistedPackages = context.getResources() - .getStringArray(com.android.internal.R.array.config_companionDevicePackages); + return isPackageAllowlisted(context, packageManagerInternal, packageName, + config_companionDevicePackages, config_companionDeviceCerts); + } + + /** + * Check if perm sync is allowlisted and auto-enabled for the package. + */ + public static boolean isPermSyncAutoEnabled(Context context, + PackageManagerInternal packageManagerInternal, String packageName) { + return isPackageAllowlisted(context, packageManagerInternal, packageName, + config_companionPermSyncEnabledPackages, config_companionPermSyncEnabledCerts); + } + + private static boolean isPackageAllowlisted(Context context, + PackageManagerInternal packageManagerInternal, String packageName, + int packagesConfig, int certsConfig) { + final String[] allowlistedPackages = context.getResources().getStringArray(packagesConfig); if (!ArrayUtils.contains(allowlistedPackages, packageName)) { Slog.d(TAG, packageName + " is not allowlisted."); return false; } final String[] allowlistedPackagesSignatureDigests = context.getResources() - .getStringArray(com.android.internal.R.array.config_companionDeviceCerts); + .getStringArray(certsConfig); final Set<String> allowlistedSignatureDigestsForRequestingPackage = new HashSet<>(); for (int i = 0; i < allowlistedPackages.length; i++) { if (allowlistedPackages[i].equals(packageName)) { diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java index d7c65c748d8f..70af49c88433 100644 --- a/services/companion/java/com/android/server/companion/virtual/InputController.java +++ b/services/companion/java/com/android/server/companion/virtual/InputController.java @@ -224,7 +224,7 @@ class InputController { token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0); mNativeWrapper.closeUinput(inputDeviceDescriptor.getNativePointer()); String phys = inputDeviceDescriptor.getPhys(); - InputManagerGlobal.getInstance().removeUniqueIdAssociation(phys); + InputManagerGlobal.getInstance().removeUniqueIdAssociationByPort(phys); // Type associations are added in the case of navigation touchpads. Those should be removed // once the input device gets closed. if (inputDeviceDescriptor.getType() == InputDeviceDescriptor.TYPE_NAVIGATION_TOUCHPAD) { @@ -287,7 +287,7 @@ class InputController { private void setUniqueIdAssociation(int displayId, String phys) { final String displayUniqueId = mDisplayManagerInternal.getDisplayInfo(displayId).uniqueId; - InputManagerGlobal.getInstance().addUniqueIdAssociation(phys, displayUniqueId); + InputManagerGlobal.getInstance().addUniqueIdAssociationByPort(phys, displayUniqueId); } boolean sendDpadKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) { @@ -789,7 +789,7 @@ class InputController { throw e; } } catch (DeviceCreationException e) { - InputManagerGlobal.getInstance().removeUniqueIdAssociation(phys); + InputManagerGlobal.getInstance().removeUniqueIdAssociationByPort(phys); throw e; } diff --git a/services/core/Android.bp b/services/core/Android.bp index 3ff05041d32d..d153c18b54b0 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -258,6 +258,8 @@ java_library_static { "core_os_flags_lib", "connectivity_flags_lib", "dreams_flags_lib", + "aconfig_new_storage_flags_lib", + "aconfigd_java_proto_lib", ], javac_shard_size: 50, javacflags: [ diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java index 748253fc9194..1a8c3b086341 100644 --- a/services/core/java/com/android/server/DropBoxManagerService.java +++ b/services/core/java/com/android/server/DropBoxManagerService.java @@ -35,7 +35,6 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Binder; import android.os.Build; -import android.os.Bundle; import android.os.BundleMerger; import android.os.Debug; import android.os.DropBoxManager; @@ -176,6 +175,16 @@ public final class DropBoxManagerService extends SystemService { } }; + private static final BundleMerger sDropboxEntryAddedExtrasMerger; + static { + sDropboxEntryAddedExtrasMerger = new BundleMerger(); + sDropboxEntryAddedExtrasMerger.setDefaultMergeStrategy(BundleMerger.STRATEGY_FIRST); + sDropboxEntryAddedExtrasMerger.setMergeStrategy(DropBoxManager.EXTRA_TIME, + BundleMerger.STRATEGY_COMPARABLE_MAX); + sDropboxEntryAddedExtrasMerger.setMergeStrategy(DropBoxManager.EXTRA_DROPPED_COUNT, + BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD); + } + private final IDropBoxManagerService.Stub mStub = new IDropBoxManagerService.Stub() { @Override public void addData(String tag, byte[] data, int flags) { @@ -284,7 +293,7 @@ public final class DropBoxManagerService extends SystemService { public void handleMessage(Message msg) { switch (msg.what) { case MSG_SEND_BROADCAST: - prepareAndSendBroadcast((Intent) msg.obj, null); + prepareAndSendBroadcast((Intent) msg.obj, false); break; case MSG_SEND_DEFERRED_BROADCAST: Intent deferredIntent; @@ -292,31 +301,42 @@ public final class DropBoxManagerService extends SystemService { deferredIntent = mDeferredMap.remove((String) msg.obj); } if (deferredIntent != null) { - prepareAndSendBroadcast(deferredIntent, - createBroadcastOptions(deferredIntent)); + prepareAndSendBroadcast(deferredIntent, true); } break; } } - private void prepareAndSendBroadcast(Intent intent, Bundle options) { + private void prepareAndSendBroadcast(Intent intent, boolean deferrable) { if (!DropBoxManagerService.this.mBooted) { intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); } + final BroadcastOptions options = BroadcastOptions.makeBasic(); if (Flags.enableReadDropboxPermission()) { - BroadcastOptions unbundledOptions = (options == null) - ? BroadcastOptions.makeBasic() : BroadcastOptions.fromBundle(options); - - unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, true); + options.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, true); + if (deferrable) { + final String matchingKey = intent.getStringExtra(DropBoxManager.EXTRA_TAG) + + "-READ_DROPBOX_DATA"; + setBroadcastOptionsForDeferral(options, matchingKey); + } getContext().sendBroadcastAsUser(intent, UserHandle.ALL, - Manifest.permission.READ_DROPBOX_DATA, unbundledOptions.toBundle()); + Manifest.permission.READ_DROPBOX_DATA, options.toBundle()); - unbundledOptions.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, false); + options.setRequireCompatChange(ENFORCE_READ_DROPBOX_DATA, false); + if (deferrable) { + final String matchingKey = intent.getStringExtra(DropBoxManager.EXTRA_TAG) + + "-READ_LOGS"; + setBroadcastOptionsForDeferral(options, matchingKey); + } getContext().sendBroadcastAsUser(intent, UserHandle.ALL, - Manifest.permission.READ_LOGS, unbundledOptions.toBundle()); + Manifest.permission.READ_LOGS, options.toBundle()); } else { + if (deferrable) { + final String matchingKey = intent.getStringExtra(DropBoxManager.EXTRA_TAG); + setBroadcastOptionsForDeferral(options, matchingKey); + } getContext().sendBroadcastAsUser(intent, UserHandle.ALL, - android.Manifest.permission.READ_LOGS, options); + android.Manifest.permission.READ_LOGS, options.toBundle()); } } @@ -328,21 +348,12 @@ public final class DropBoxManagerService extends SystemService { return dropboxIntent; } - private Bundle createBroadcastOptions(Intent intent) { - final BundleMerger extrasMerger = new BundleMerger(); - extrasMerger.setDefaultMergeStrategy(BundleMerger.STRATEGY_FIRST); - extrasMerger.setMergeStrategy(DropBoxManager.EXTRA_TIME, - BundleMerger.STRATEGY_COMPARABLE_MAX); - extrasMerger.setMergeStrategy(DropBoxManager.EXTRA_DROPPED_COUNT, - BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD); - - return BroadcastOptions.makeBasic() - .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED) + private void setBroadcastOptionsForDeferral(BroadcastOptions options, String matchingKey) { + options.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED) .setDeliveryGroupMatchingKey(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED, - intent.getStringExtra(DropBoxManager.EXTRA_TAG)) - .setDeliveryGroupExtrasMerger(extrasMerger) - .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE) - .toBundle(); + matchingKey) + .setDeliveryGroupExtrasMerger(sDropboxEntryAddedExtrasMerger) + .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE); } /** diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java index c7a83693e063..d04dbc2f417e 100644 --- a/services/core/java/com/android/server/PinnerService.java +++ b/services/core/java/com/android/server/PinnerService.java @@ -121,7 +121,6 @@ public final class PinnerService extends SystemService { SystemProperties.getBoolean("pinner.use_pinlist", true); private static final int MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); // 80MB max for camera app. - private static final int MAX_HOME_PIN_SIZE = 6 * (1 << 20); // 6MB max for home app. private static final int MAX_ASSISTANT_PIN_SIZE = 60 * (1 << 20); // 60MB max for assistant app. public static final String ANON_REGION_STAT_NAME = "[anon]"; @@ -176,7 +175,7 @@ public final class PinnerService extends SystemService { // Resource-configured pinner flags; private final boolean mConfiguredToPinCamera; - private final boolean mConfiguredToPinHome; + private final int mConfiguredHomePinBytes; private final boolean mConfiguredToPinAssistant; private final int mConfiguredWebviewPinBytes; @@ -238,8 +237,8 @@ public final class PinnerService extends SystemService { mDeviceConfigInterface = mInjector.getDeviceConfigInterface(); mConfiguredToPinCamera = context.getResources().getBoolean( com.android.internal.R.bool.config_pinnerCameraApp); - mConfiguredToPinHome = context.getResources().getBoolean( - com.android.internal.R.bool.config_pinnerHomeApp); + mConfiguredHomePinBytes = context.getResources().getInteger( + com.android.internal.R.integer.config_pinnerHomePinBytes); mConfiguredToPinAssistant = context.getResources().getBoolean( com.android.internal.R.bool.config_pinnerAssistantApp); mConfiguredWebviewPinBytes = context.getResources().getInteger( @@ -390,7 +389,7 @@ public final class PinnerService extends SystemService { @Override public void onChange(boolean selfChange, Uri uri) { if (userSetupCompleteUri.equals(uri)) { - if (mConfiguredToPinHome) { + if (mConfiguredHomePinBytes > 0) { sendPinAppMessage(KEY_HOME, ActivityManager.getCurrentUser(), true /* force */); } @@ -594,7 +593,7 @@ public final class PinnerService extends SystemService { Slog.i(TAG, "Pinner - skip pinning camera app"); } - if (mConfiguredToPinHome) { + if (mConfiguredHomePinBytes > 0) { pinKeys.add(KEY_HOME); } if (mConfiguredToPinAssistant) { @@ -815,7 +814,7 @@ public final class PinnerService extends SystemService { case KEY_CAMERA: return MAX_CAMERA_PIN_SIZE; case KEY_HOME: - return MAX_HOME_PIN_SIZE; + return mConfiguredHomePinBytes; case KEY_ASSISTANT: return MAX_ASSISTANT_PIN_SIZE; default: diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index f1776f4f9148..f1d358486333 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -1986,11 +1986,9 @@ final class UiModeManagerService extends SystemService { // the status bar should be totally disabled, the calls below will // have no effect until the device is unlocked. if (mStatusBarManager != null) { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - if (mCarModeEnabled) { - info.setNotificationTickerDisabled(true); - } - mStatusBarManager.requestDisabledComponent(info, "adjustStatusBarCarModeLocked"); + mStatusBarManager.disable(mCarModeEnabled + ? StatusBarManager.DISABLE_NOTIFICATION_TICKER + : StatusBarManager.DISABLE_NONE); } if (mNotificationManager == null) { diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 1015ad9fe1da..ac9ed0da95a5 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -4532,8 +4532,13 @@ public class AccountManagerService public Account[] getAccountsAsUser(String type, int userId, String opPackageName) { int callingUid = Binder.getCallingUid(); mAppOpsManager.checkPackage(callingUid, opPackageName); - return getAccountsAsUserForPackage(type, userId, opPackageName /* callingPackage */, -1, - opPackageName, false /* includeUserManagedNotVisible */); + try { + return getAccountsAsUserForPackage(type, userId, opPackageName /* callingPackage */, -1, + opPackageName, false /* includeUserManagedNotVisible */); + } catch (SQLiteCantOpenDatabaseException e) { + Log.e(TAG, "Could not get accounts for user " + userId, e); + return new Account[]{}; + } } @NonNull diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 4ca9e3380a92..94bf813d3696 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -157,6 +157,7 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.RemoteServiceException.ForegroundServiceDidNotStartInTimeException; +import android.app.RemoteServiceException.ForegroundServiceDidNotStopInTimeException; import android.app.Service; import android.app.ServiceStartArgs; import android.app.StartForegroundCalledOnStoppedServiceException; @@ -784,7 +785,7 @@ public final class ActiveServices { ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, "SERVICE_FOREGROUND_TIMEOUT"); this.mFGSAnrTimer = new ServiceAnrTimer(service, - ActivityManagerService.SERVICE_FGS_ANR_TIMEOUT_MSG, + ActivityManagerService.SERVICE_FGS_CRASH_TIMEOUT_MSG, "FGS_TIMEOUT"); } @@ -1168,9 +1169,7 @@ public final class ActiveServices { } private boolean shouldAllowBootCompletedStart(ServiceRecord r, int foregroundServiceType) { - @PowerExemptionManager.ReasonCode final int fgsStartReasonCode = - r.mInfoTempFgsAllowListReason != null ? r.mInfoTempFgsAllowListReason.mReasonCode - : REASON_DENIED; + @PowerExemptionManager.ReasonCode final int fgsStartReasonCode = r.getFgsAllowStart(); if (Flags.fgsBootCompleted() && CompatChanges.isChangeEnabled(FGS_BOOT_COMPLETED_RESTRICTIONS, r.appInfo.uid) && fgsStartReasonCode == PowerExemptionManager.REASON_BOOT_COMPLETED) { @@ -2454,10 +2453,21 @@ public final class ActiveServices { } else if (lastTimeOutAt > 0) { // Time limit was exhausted within the past 24 hours and the app // has not been in the TOP state since then, throw an exception. - throw new ForegroundServiceStartNotAllowedException("Time limit" - + " already exhausted for foreground service type " + final String exceptionMsg = "Time limit already exhausted for" + + " foreground service type " + ServiceInfo.foregroundServiceTypeToLabel( - foregroundServiceType)); + foregroundServiceType); + // Only throw an exception if the new ANR behavior + // ("do nothing") is not gated or the new crashing logic gate + // is enabled; otherwise, reset the limit temporarily. + if (!android.app.Flags.gateFgsTimeoutAnrBehavior() + || android.app.Flags.enableFgsTimeoutCrashBehavior()) { + throw new ForegroundServiceStartNotAllowedException( + exceptionMsg); + } else { + Slog.wtf(TAG, exceptionMsg); + fgsTypeInfo.reset(); + } } } } else { @@ -3929,12 +3939,12 @@ public final class ActiveServices { Slog.w(TAG_SERVICE, "Exception from scheduleTimeoutServiceForType: " + e); } - // ANR the service after giving the service some time to clean up. - mFGSAnrTimer.start(sr, mAm.mConstants.mFgsAnrExtraWaitDuration); + // Crash the service after giving the service some time to clean up. + mFGSAnrTimer.start(sr, mAm.mConstants.mFgsCrashExtraWaitDuration); } } - void onFgsAnrTimeout(ServiceRecord sr) { + void onFgsCrashTimeout(ServiceRecord sr) { final int fgsType = getTimeLimitedFgsType(sr.foregroundServiceType); if (fgsType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) { return; // no timed out FGS type was found (either it was stopped or it switched types) @@ -3943,20 +3953,42 @@ public final class ActiveServices { + ServiceInfo.foregroundServiceTypeToLabel(fgsType) + " did not stop within its timeout: " + sr.getComponentName(); - final TimeoutRecord tr = TimeoutRecord.forFgsTimeout(reason); - tr.mLatencyTracker.waitingOnAMSLockStarted(); - synchronized (mAm) { - tr.mLatencyTracker.waitingOnAMSLockEnded(); + if (android.app.Flags.gateFgsTimeoutAnrBehavior()) { + // Log a WTF instead of throwing an ANR while the new behavior is gated. + Slog.wtf(TAG, reason); + return; + } - Slog.e(TAG_SERVICE, "FGS ANR'ed: " + sr); - traceInstant("FGS ANR: ", sr); - if (sr.app != null) { - mAm.appNotResponding(sr.app, tr); + if (android.app.Flags.enableFgsTimeoutCrashBehavior()) { + // Crash the app + synchronized (mAm) { + Slog.e(TAG_SERVICE, "FGS Crashed: " + sr); + traceInstant("FGS Crash: ", sr); + if (sr.app != null) { + mAm.crashApplicationWithTypeWithExtras(sr.app.uid, sr.app.getPid(), + sr.app.info.packageName, sr.app.userId, reason, false /*force*/, + ForegroundServiceDidNotStopInTimeException.TYPE_ID, + ForegroundServiceDidNotStopInTimeException + .createExtrasForService(sr.getComponentName())); + } } + } else { + // ANR the app if the new crash behavior is not enabled + final TimeoutRecord tr = TimeoutRecord.forFgsTimeout(reason); + tr.mLatencyTracker.waitingOnAMSLockStarted(); + synchronized (mAm) { + tr.mLatencyTracker.waitingOnAMSLockEnded(); - // TODO: Can we close the ANR dialog here, if it's still shown? Currently, the ANR - // dialog really doesn't remember the "cause" (especially if there have been multiple - // ANRs), so it's not doable. + Slog.e(TAG_SERVICE, "FGS ANR'ed: " + sr); + traceInstant("FGS ANR: ", sr); + if (sr.app != null) { + mAm.appNotResponding(sr.app, tr); + } + + // TODO: Can we close the ANR dialog here, if it's still shown? Currently, the ANR + // dialog really doesn't remember the "cause" (especially if there have been + // multiple ANRs), so it's not doable. + } } } diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 9e06b7535cdf..26aa0535d43e 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -1115,17 +1115,17 @@ final class ActivityManagerConstants extends ContentObserver { /** * If a service of a timeout-enforced type doesn't finish within this duration after its - * timeout, then we'll declare an ANR. + * timeout, then we'll crash the app. * i.e. if the time limit for a type is 1 hour, and this extra duration is 10 seconds, then - * the app will be ANR'ed 1 hour and 10 seconds after it started. + * the app will crash 1 hour and 10 seconds after it started. */ - private static final String KEY_FGS_ANR_EXTRA_WAIT_DURATION = "fgs_anr_extra_wait_duration"; + private static final String KEY_FGS_CRASH_EXTRA_WAIT_DURATION = "fgs_crash_extra_wait_duration"; - /** @see #KEY_FGS_ANR_EXTRA_WAIT_DURATION */ - static final long DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION = 10_000; + /** @see #KEY_FGS_CRASH_EXTRA_WAIT_DURATION */ + static final long DEFAULT_FGS_CRASH_EXTRA_WAIT_DURATION = 10_000; - /** @see #KEY_FGS_ANR_EXTRA_WAIT_DURATION */ - public volatile long mFgsAnrExtraWaitDuration = DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION; + /** @see #KEY_FGS_CRASH_EXTRA_WAIT_DURATION */ + public volatile long mFgsCrashExtraWaitDuration = DEFAULT_FGS_CRASH_EXTRA_WAIT_DURATION; /** @see #KEY_USE_TIERED_CACHED_ADJ */ public boolean USE_TIERED_CACHED_ADJ = DEFAULT_USE_TIERED_CACHED_ADJ; @@ -1315,8 +1315,8 @@ final class ActivityManagerConstants extends ContentObserver { case KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION: updateShortFgsAnrExtraWaitDuration(); break; - case KEY_FGS_ANR_EXTRA_WAIT_DURATION: - updateFgsAnrExtraWaitDuration(); + case KEY_FGS_CRASH_EXTRA_WAIT_DURATION: + updateFgsCrashExtraWaitDuration(); break; case KEY_PROACTIVE_KILLS_ENABLED: updateProactiveKillsEnabled(); @@ -2199,11 +2199,11 @@ final class ActivityManagerConstants extends ContentObserver { DEFAULT_DATA_SYNC_FGS_TIMEOUT_DURATION); } - private void updateFgsAnrExtraWaitDuration() { - mFgsAnrExtraWaitDuration = DeviceConfig.getLong( + private void updateFgsCrashExtraWaitDuration() { + mFgsCrashExtraWaitDuration = DeviceConfig.getLong( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - KEY_FGS_ANR_EXTRA_WAIT_DURATION, - DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION); + KEY_FGS_CRASH_EXTRA_WAIT_DURATION, + DEFAULT_FGS_CRASH_EXTRA_WAIT_DURATION); } private void updateEnableWaitForFinishAttachApplication() { @@ -2456,8 +2456,8 @@ final class ActivityManagerConstants extends ContentObserver { pw.print("="); pw.println(mMediaProcessingFgsTimeoutDuration); pw.print(" "); pw.print(KEY_DATA_SYNC_FGS_TIMEOUT_DURATION); pw.print("="); pw.println(mDataSyncFgsTimeoutDuration); - pw.print(" "); pw.print(KEY_FGS_ANR_EXTRA_WAIT_DURATION); - pw.print("="); pw.println(mFgsAnrExtraWaitDuration); + pw.print(" "); pw.print(KEY_FGS_CRASH_EXTRA_WAIT_DURATION); + pw.print("="); pw.println(mFgsCrashExtraWaitDuration); pw.print(" "); pw.print(KEY_USE_TIERED_CACHED_ADJ); pw.print("="); pw.println(USE_TIERED_CACHED_ADJ); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 00c2df67c8f0..46ed1fd79874 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1670,7 +1670,7 @@ public class ActivityManagerService extends IActivityManager.Stub static final int BIND_APPLICATION_TIMEOUT_SOFT_MSG = 82; static final int BIND_APPLICATION_TIMEOUT_HARD_MSG = 83; static final int SERVICE_FGS_TIMEOUT_MSG = 84; - static final int SERVICE_FGS_ANR_TIMEOUT_MSG = 85; + static final int SERVICE_FGS_CRASH_TIMEOUT_MSG = 85; static final int FIRST_BROADCAST_QUEUE_MSG = 200; @@ -2041,8 +2041,8 @@ public class ActivityManagerService extends IActivityManager.Stub case SERVICE_FGS_TIMEOUT_MSG: { mServices.onFgsTimeout((ServiceRecord) msg.obj); } break; - case SERVICE_FGS_ANR_TIMEOUT_MSG: { - mServices.onFgsAnrTimeout((ServiceRecord) msg.obj); + case SERVICE_FGS_CRASH_TIMEOUT_MSG: { + mServices.onFgsCrashTimeout((ServiceRecord) msg.obj); } break; } } diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 3cea01464c10..a182a106ed8c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -115,6 +115,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.DebugUtils; import android.util.DisplayMetrics; +import android.util.SparseArray; import android.util.TeeWriter; import android.util.proto.ProtoOutputStream; import android.view.Choreographer; @@ -275,9 +276,9 @@ final class ActivityManagerShellCommand extends ShellCommand { case "compact": return runCompact(pw); case "freeze": - return runFreeze(pw); + return runFreeze(pw, true); case "unfreeze": - return runUnfreeze(pw); + return runFreeze(pw, false); case "instrument": getOutPrintWriter().println("Error: must be invoked through 'am instrument'."); return -1; @@ -1203,45 +1204,27 @@ final class ActivityManagerShellCommand extends ShellCommand { } @NeverCompile - int runFreeze(PrintWriter pw) throws RemoteException { + int runFreeze(PrintWriter pw, boolean freeze) throws RemoteException { String freezerOpt = getNextOption(); boolean isSticky = false; - if (freezerOpt != null) { - isSticky = freezerOpt.equals("--sticky"); - } - ProcessRecord app = getProcessFromShell(); - if (app == null) { - getErrPrintWriter().println("Error: could not find process"); - return -1; - } - pw.println("Freezing pid: " + app.mPid + " sticky=" + isSticky); - synchronized (mInternal) { - synchronized (mInternal.mProcLock) { - app.mOptRecord.setFreezeSticky(isSticky); - mInternal.mOomAdjuster.mCachedAppOptimizer.forceFreezeAppAsyncLSP(app); - } - } - return 0; - } - @NeverCompile - int runUnfreeze(PrintWriter pw) throws RemoteException { - String freezerOpt = getNextOption(); - boolean isSticky = false; if (freezerOpt != null) { isSticky = freezerOpt.equals("--sticky"); } - ProcessRecord app = getProcessFromShell(); - if (app == null) { - getErrPrintWriter().println("Error: could not find process"); + ProcessRecord proc = getProcessFromShell(); + if (proc == null) { return -1; } - pw.println("Unfreezing pid: " + app.mPid); + pw.print(freeze ? "Freezing" : "Unfreezing"); + pw.print(" process " + proc.processName); + pw.println(" (" + proc.mPid + ") sticky=" + isSticky); synchronized (mInternal) { synchronized (mInternal.mProcLock) { - synchronized (mInternal.mOomAdjuster.mCachedAppOptimizer.mFreezerLock) { - app.mOptRecord.setFreezeSticky(isSticky); - mInternal.mOomAdjuster.mCachedAppOptimizer.unfreezeAppInternalLSP(app, 0, + proc.mOptRecord.setFreezeSticky(isSticky); + if (freeze) { + mInternal.mOomAdjuster.mCachedAppOptimizer.forceFreezeAppAsyncLSP(proc); + } else { + mInternal.mOomAdjuster.mCachedAppOptimizer.unfreezeAppInternalLSP(proc, 0, true); } } @@ -1250,43 +1233,42 @@ final class ActivityManagerShellCommand extends ShellCommand { } /** - * Parses from the shell the process name and user id if provided and provides the corresponding - * {@link ProcessRecord)} If no user is provided, it will fallback to current user. - * Example usage: {@code <processname> --user current} or {@code <processname>} - * @return process record of process, null if none found. + * Parses from the shell the pid or process name and provides the corresponding + * {@link ProcessRecord}. + * Example usage: {@code <processname>} or {@code <pid>} + * @return process record of process, null if none or more than one found. * @throws RemoteException */ @NeverCompile ProcessRecord getProcessFromShell() throws RemoteException { - ProcessRecord app; - String processName = getNextArgRequired(); - synchronized (mInternal.mProcLock) { - // Default to current user - int userId = getUserIdFromShellOrFallback(); - final int uid = - mInternal.getPackageManagerInternal().getPackageUid(processName, 0, userId); - app = mInternal.getProcessRecordLocked(processName, uid); - } - return app; - } + ProcessRecord proc = null; + String process = getNextArgRequired(); + try { + int pid = Integer.parseInt(process); + synchronized (mInternal.mPidsSelfLocked) { + proc = mInternal.mPidsSelfLocked.get(pid); + } + } catch (NumberFormatException e) { + // Fallback to process name if it's not a valid pid + } - /** - * @return User id from command line provided in the form of - * {@code --user <userid|current|all>} and if the argument is not found it will fallback - * to current user. - * @throws RemoteException - */ - @NeverCompile - int getUserIdFromShellOrFallback() throws RemoteException { - int userId = mInterface.getCurrentUserId(); - String userOpt = getNextOption(); - if (userOpt != null && "--user".equals(userOpt)) { - int inputUserId = UserHandle.parseUserArg(getNextArgRequired()); - if (inputUserId != UserHandle.USER_CURRENT) { - userId = inputUserId; + if (proc == null) { + synchronized (mInternal.mProcLock) { + ArrayMap<String, SparseArray<ProcessRecord>> all = + mInternal.mProcessList.getProcessNamesLOSP().getMap(); + SparseArray<ProcessRecord> procs = all.get(process); + if (procs == null || procs.size() == 0) { + getErrPrintWriter().println("Error: could not find process"); + return null; + } else if (procs.size() > 1) { + getErrPrintWriter().println("Error: more than one processes found"); + return null; + } + proc = procs.valueAt(0); } } - return userId; + + return proc; } int runDumpHeap(PrintWriter pw) throws RemoteException { @@ -4306,24 +4288,26 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" --allow-background-activity-starts: The receiver may start activities"); pw.println(" even if in the background."); pw.println(" --async: Send without waiting for the completion of the receiver."); - pw.println(" compact [some|full] <process_name> [--user <USER_ID>]"); - pw.println(" Perform a single process compaction."); + pw.println(" compact {some|full} <PROCESS>"); + pw.println(" Perform a single process compaction. The given <PROCESS> argument"); + pw.println(" may be either a process name or pid."); pw.println(" some: execute file compaction."); pw.println(" full: execute anon + file compaction."); - pw.println(" system: system compaction."); pw.println(" compact system"); pw.println(" Perform a full system compaction."); - pw.println(" compact native [some|full] <pid>"); + pw.println(" compact native {some|full} <pid>"); pw.println(" Perform a native compaction for process with <pid>."); pw.println(" some: execute file compaction."); pw.println(" full: execute anon + file compaction."); - pw.println(" freeze [--sticky] <processname> [--user <USER_ID>]"); - pw.println(" Freeze a process."); - pw.println(" --sticky: persists the frozen state for the process lifetime or"); + pw.println(" freeze [--sticky] <PROCESS>"); + pw.println(" Freeze a process. The given <PROCESS> argument"); + pw.println(" may be either a process name or pid. Options are:"); + pw.println(" --sticky: persists the frozen state for the process lifetime or"); pw.println(" until an unfreeze is triggered via shell"); - pw.println(" unfreeze [--sticky] <processname> [--user <USER_ID>]"); - pw.println(" Unfreeze a process."); - pw.println(" --sticky: persists the unfrozen state for the process lifetime or"); + pw.println(" unfreeze [--sticky] <PROCESS>"); + pw.println(" Unfreeze a process. The given <PROCESS> argument"); + pw.println(" may be either a process name or pid. Options are:"); + pw.println(" --sticky: persists the unfrozen state for the process lifetime or"); pw.println(" until a freeze is triggered via shell"); pw.println(" instrument [-r] [-e <NAME> <VALUE>] [-p <FILE>] [-w]"); pw.println(" [--user <USER_ID> | current]"); diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java index f2b9b25d6097..31704c442290 100644 --- a/services/core/java/com/android/server/am/ConnectionRecord.java +++ b/services/core/java/com/android/server/am/ConnectionRecord.java @@ -136,6 +136,13 @@ final class ConnectionRecord implements OomAdjusterModernImpl.Connection{ false, oomAdjReason, UNKNOWN_ADJ, false, false); } + @Override + public boolean canAffectCapabilities() { + return hasFlag(Context.BIND_INCLUDE_CAPABILITIES + | Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS); + } + + public long getFlags() { return flags; } diff --git a/services/core/java/com/android/server/am/ContentProviderConnection.java b/services/core/java/com/android/server/am/ContentProviderConnection.java index 3988277ab525..ae5ae0133e1b 100644 --- a/services/core/java/com/android/server/am/ContentProviderConnection.java +++ b/services/core/java/com/android/server/am/ContentProviderConnection.java @@ -82,6 +82,12 @@ public final class ContentProviderConnection extends Binder implements false, oomAdjReason, UNKNOWN_ADJ, false, false); } + @Override + public boolean canAffectCapabilities() { + return false; + } + + public void startAssociationIfNeeded() { // If we don't already have an active association, create one... but only if this // is an association between two different processes. diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index cc6ae5ca03e3..8647750d510f 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -3277,7 +3277,13 @@ public class OomAdjuster { } final int curSchedGroup = state.getCurrentSchedulingGroup(); - if (state.getSetSchedGroup() != curSchedGroup) { + if (app.getWaitingToKill() != null && app.mReceivers.numberOfCurReceivers() == 0 + && ActivityManager.isProcStateBackground(state.getCurProcState()) + && !state.hasStartedServices()) { + app.killLocked(app.getWaitingToKill(), ApplicationExitInfo.REASON_USER_REQUESTED, + ApplicationExitInfo.SUBREASON_REMOVE_TASK, true); + success = false; + } else if (state.getSetSchedGroup() != curSchedGroup) { int oldSchedGroup = state.getSetSchedGroup(); state.setSetSchedGroup(curSchedGroup); if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) { @@ -3285,75 +3291,67 @@ public class OomAdjuster { + " to " + curSchedGroup + ": " + state.getAdjType(); reportOomAdjMessageLocked(TAG_OOM_ADJ, msg); } - if (app.getWaitingToKill() != null && app.mReceivers.numberOfCurReceivers() == 0 - && ActivityManager.isProcStateBackground(state.getSetProcState()) - && !state.hasStartedServices()) { - app.killLocked(app.getWaitingToKill(), ApplicationExitInfo.REASON_USER_REQUESTED, - ApplicationExitInfo.SUBREASON_REMOVE_TASK, true); - success = false; - } else { - int processGroup; - switch (curSchedGroup) { - case SCHED_GROUP_BACKGROUND: - processGroup = THREAD_GROUP_BACKGROUND; - break; - case SCHED_GROUP_TOP_APP: - case SCHED_GROUP_TOP_APP_BOUND: - processGroup = THREAD_GROUP_TOP_APP; - break; - case SCHED_GROUP_RESTRICTED: - processGroup = THREAD_GROUP_RESTRICTED; - break; - default: - processGroup = THREAD_GROUP_DEFAULT; - break; - } - mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage( - 0 /* unused */, app.getPid(), processGroup, app.processName)); - try { - final int renderThreadTid = app.getRenderThreadTid(); - if (curSchedGroup == SCHED_GROUP_TOP_APP) { - // do nothing if we already switched to RT - if (oldSchedGroup != SCHED_GROUP_TOP_APP) { - app.getWindowProcessController().onTopProcChanged(); - if (app.useFifoUiScheduling()) { - // Switch UI pipeline for app to SCHED_FIFO - state.setSavedPriority(Process.getThreadPriority(app.getPid())); - ActivityManagerService.setFifoPriority(app, true /* enable */); - } else { - // Boost priority for top app UI and render threads - setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST); - if (renderThreadTid != 0) { - try { - setThreadPriority(renderThreadTid, - THREAD_PRIORITY_TOP_APP_BOOST); - } catch (IllegalArgumentException e) { - // thread died, ignore - } - } - } - } - } else if (oldSchedGroup == SCHED_GROUP_TOP_APP - && curSchedGroup != SCHED_GROUP_TOP_APP) { + int processGroup; + switch (curSchedGroup) { + case SCHED_GROUP_BACKGROUND: + processGroup = THREAD_GROUP_BACKGROUND; + break; + case SCHED_GROUP_TOP_APP: + case SCHED_GROUP_TOP_APP_BOUND: + processGroup = THREAD_GROUP_TOP_APP; + break; + case SCHED_GROUP_RESTRICTED: + processGroup = THREAD_GROUP_RESTRICTED; + break; + default: + processGroup = THREAD_GROUP_DEFAULT; + break; + } + mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage( + 0 /* unused */, app.getPid(), processGroup, app.processName)); + try { + final int renderThreadTid = app.getRenderThreadTid(); + if (curSchedGroup == SCHED_GROUP_TOP_APP) { + // do nothing if we already switched to RT + if (oldSchedGroup != SCHED_GROUP_TOP_APP) { app.getWindowProcessController().onTopProcChanged(); if (app.useFifoUiScheduling()) { - // Reset UI pipeline to SCHED_OTHER - ActivityManagerService.setFifoPriority(app, false /* enable */); - setThreadPriority(app.getPid(), state.getSavedPriority()); + // Switch UI pipeline for app to SCHED_FIFO + state.setSavedPriority(Process.getThreadPriority(app.getPid())); + ActivityManagerService.setFifoPriority(app, true /* enable */); } else { - // Reset priority for top app UI and render threads - setThreadPriority(app.getPid(), 0); - } - - if (renderThreadTid != 0) { - setThreadPriority(renderThreadTid, THREAD_PRIORITY_DISPLAY); + // Boost priority for top app UI and render threads + setThreadPriority(app.getPid(), THREAD_PRIORITY_TOP_APP_BOOST); + if (renderThreadTid != 0) { + try { + setThreadPriority(renderThreadTid, + THREAD_PRIORITY_TOP_APP_BOOST); + } catch (IllegalArgumentException e) { + // thread died, ignore + } + } } } - } catch (Exception e) { - if (DEBUG_ALL) { - Slog.w(TAG, "Failed setting thread priority of " + app.getPid(), e); + } else if (oldSchedGroup == SCHED_GROUP_TOP_APP + && curSchedGroup != SCHED_GROUP_TOP_APP) { + app.getWindowProcessController().onTopProcChanged(); + if (app.useFifoUiScheduling()) { + // Reset UI pipeline to SCHED_OTHER + ActivityManagerService.setFifoPriority(app, false /* enable */); + setThreadPriority(app.getPid(), state.getSavedPriority()); + } else { + // Reset priority for top app UI and render threads + setThreadPriority(app.getPid(), 0); + } + + if (renderThreadTid != 0) { + setThreadPriority(renderThreadTid, THREAD_PRIORITY_DISPLAY); } } + } catch (Exception e) { + if (DEBUG_ALL) { + Slog.w(TAG, "Failed setting thread priority of " + app.getPid(), e); + } } } if (state.hasRepForegroundActivities() != state.hasForegroundActivities()) { diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java index 3268b661df65..9600317faca1 100644 --- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java +++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java @@ -16,6 +16,7 @@ package com.android.server.am; +import static android.app.ActivityManager.PROCESS_CAPABILITY_BFSL; import static android.app.ActivityManager.PROCESS_STATE_BACKUP; import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; @@ -521,6 +522,11 @@ public class OomAdjusterModernImpl extends OomAdjuster { */ void computeHostOomAdjLSP(OomAdjuster oomAdjuster, ProcessRecord host, ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll, int oomAdjReason, int cachedAdj); + + /** + * Returns true if this connection can propagate capabilities. + */ + boolean canAffectCapabilities(); } /** @@ -553,16 +559,29 @@ public class OomAdjusterModernImpl extends OomAdjuster { */ private class ComputeConnectionIgnoringReachableClientsConsumer implements BiConsumer<Connection, ProcessRecord> { - public OomAdjusterArgs args = null; + private OomAdjusterArgs mArgs = null; + public boolean hasReachableClient = false; + + public void init(OomAdjusterArgs args) { + mArgs = args; + hasReachableClient = false; + } @Override public void accept(Connection conn, ProcessRecord client) { - final ProcessRecord host = args.mApp; - final ProcessRecord topApp = args.mTopApp; - final long now = args.mNow; - final @OomAdjReason int oomAdjReason = args.mOomAdjReason; + final ProcessRecord host = mArgs.mApp; + final ProcessRecord topApp = mArgs.mTopApp; + final long now = mArgs.mNow; + final @OomAdjReason int oomAdjReason = mArgs.mOomAdjReason; + + if (client.mState.isReachable()) { + hasReachableClient = true; + return; + } - if (client.mState.isReachable()) return; + if (unimportantConnectionLSP(conn, host, client)) { + return; + } conn.computeHostOomAdjLSP(OomAdjusterModernImpl.this, host, client, now, topApp, false, oomAdjReason, UNKNOWN_ADJ); @@ -591,6 +610,10 @@ public class OomAdjusterModernImpl extends OomAdjuster { final int prevProcState = host.mState.getCurProcState(); final int prevAdj = host.mState.getCurRawAdj(); + if (unimportantConnectionLSP(conn, host, client)) { + return; + } + conn.computeHostOomAdjLSP(OomAdjusterModernImpl.this, host, client, now, topApp, fullUpdate, oomAdjReason, cachedAdj); @@ -874,7 +897,7 @@ public class OomAdjusterModernImpl extends OomAdjuster { // processes cannot change as a part of this update, their current values can be used // right now. mProcessRecordProcStateNodes.resetLastNodes(); - initReachableStatesLSP(reachables, mTmpOomAdjusterArgs); + initReachableStatesLSP(reachables, targets.size(), mTmpOomAdjusterArgs); // Set adj last nodes now, this way a process will only be reevaluated during the adj node // iteration if they adj score changed during the procState node iteration. @@ -914,8 +937,6 @@ public class OomAdjusterModernImpl extends OomAdjuster { /** * Mark all processes reachable from the {@code reachables} processes and add them to the * provided {@code reachables} list (targets excluded). - * - * Returns true if a cycle exists within the reachable process graph. */ @GuardedBy({"mService", "mProcLock"}) private void collectAndMarkReachableProcessesLSP(ArrayList<ProcessRecord> reachables) { @@ -930,8 +951,35 @@ public class OomAdjusterModernImpl extends OomAdjuster { * Calculate initial importance states for {@code reachables} and update their slot position * if necessary. */ - private void initReachableStatesLSP(ArrayList<ProcessRecord> reachables, OomAdjusterArgs args) { - for (int i = 0, size = reachables.size(); i < size; i++) { + private void initReachableStatesLSP(ArrayList<ProcessRecord> reachables, int targetCount, + OomAdjusterArgs args) { + int i = 0; + boolean initReachables = !Flags.skipUnimportantConnections(); + for (; i < targetCount && !initReachables; i++) { + final ProcessRecord target = reachables.get(i); + final int prevProcState = target.mState.getCurProcState(); + final int prevAdj = target.mState.getCurRawAdj(); + final int prevCapability = target.mState.getCurCapability(); + final boolean prevShouldNotFreeze = target.mOptRecord.shouldNotFreeze(); + + args.mApp = target; + // If target client is a reachable, reachables need to be reinited in case this + // client is important enough to change this target in the computeConnection step. + initReachables |= computeOomAdjIgnoringReachablesLSP(args); + // If target lowered in importance, reachables need to be reinited because this + // target may have been the source of a reachable's current importance. + initReachables |= selfImportanceLoweredLSP(target, prevProcState, prevAdj, + prevCapability, prevShouldNotFreeze); + + updateProcStateSlot(target, prevProcState); + updateAdjSlot(target, prevAdj); + } + + if (!initReachables) { + return; + } + + for (int size = reachables.size(); i < size; i++) { final ProcessRecord reachable = reachables.get(i); final int prevProcState = reachable.mState.getCurProcState(); final int prevAdj = reachable.mState.getCurRawAdj(); @@ -948,9 +996,11 @@ public class OomAdjusterModernImpl extends OomAdjuster { * Calculate initial importance states for {@code app}. * Processes not marked reachable cannot change as a part of this update, so connections from * those process can be calculated now. + * + * Returns true if any client connection was skipped due to a reachablity cycle. */ @GuardedBy({"mService", "mProcLock"}) - private void computeOomAdjIgnoringReachablesLSP(OomAdjusterArgs args) { + private boolean computeOomAdjIgnoringReachablesLSP(OomAdjusterArgs args) { final ProcessRecord app = args.mApp; final ProcessRecord topApp = args.mTopApp; final long now = args.mNow; @@ -958,8 +1008,9 @@ public class OomAdjusterModernImpl extends OomAdjuster { computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, false, now, false, false, oomAdjReason, false); - mComputeConnectionIgnoringReachableClientsConsumer.args = args; + mComputeConnectionIgnoringReachableClientsConsumer.init(args); forEachClientConnectionLSP(app, mComputeConnectionIgnoringReachableClientsConsumer); + return mComputeConnectionIgnoringReachableClientsConsumer.hasReachableClient; } /** @@ -1039,6 +1090,7 @@ public class OomAdjusterModernImpl extends OomAdjuster { } else { client = cr.binding.client; } + if (client == null || client == app) continue; connectionConsumer.accept(cr, client); } } @@ -1053,4 +1105,66 @@ public class OomAdjusterModernImpl extends OomAdjuster { } } } + + /** + * Returns true if at least one the provided values is more important than those in {@code app}. + */ + @GuardedBy({"mService", "mProcLock"}) + private static boolean selfImportanceLoweredLSP(ProcessRecord app, int prevProcState, + int prevAdj, int prevCapability, boolean prevShouldNotFreeze) { + if (app.mState.getCurProcState() > prevProcState) { + return true; + } + if (app.mState.getCurRawAdj() > prevAdj) { + return true; + } + if ((app.mState.getCurCapability() & prevCapability) != prevCapability) { + return true; + } + if (!app.mOptRecord.shouldNotFreeze() && prevShouldNotFreeze) { + // No long marked as should not freeze. + return true; + } + return false; + } + + /** + * Returns whether a host connection evaluation can be skipped due to lack of importance. + * Note: the client and host need to be provided as well for the isolated and sandbox + * scenarios. + */ + @GuardedBy({"mService", "mProcLock"}) + private static boolean unimportantConnectionLSP(Connection conn, + ProcessRecord host, ProcessRecord client) { + if (!Flags.skipUnimportantConnections()) { + // Feature not enabled, just return false so the connection is evaluated. + return false; + } + if (host.mState.getCurProcState() > client.mState.getCurProcState()) { + return false; + } + if (host.mState.getCurRawAdj() > client.mState.getCurRawAdj()) { + return false; + } + final int serviceCapability = host.mState.getCurCapability(); + final int clientCapability = client.mState.getCurCapability(); + if ((serviceCapability & clientCapability) != clientCapability) { + // Client has a capability the host does not have. + if ((clientCapability & PROCESS_CAPABILITY_BFSL) == PROCESS_CAPABILITY_BFSL + && (serviceCapability & PROCESS_CAPABILITY_BFSL) == 0) { + // The BFSL capability does not need a flag to propagate. + return false; + } + if (conn.canAffectCapabilities()) { + // One of these bind flags may propagate that capability. + return false; + } + } + + if (!host.mOptRecord.shouldNotFreeze() && client.mOptRecord.shouldNotFreeze()) { + // If the client is marked as should not freeze, so should the host. + return false; + } + 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 95206212c99d..827db579f111 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -20,6 +20,8 @@ import android.annotation.NonNull; import android.content.ContentResolver; import android.database.ContentObserver; import android.net.Uri; +import android.net.LocalSocketAddress; +import android.net.LocalSocket; import android.os.AsyncTask; import android.os.Build; import android.os.SystemProperties; @@ -30,6 +32,14 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import android.aconfigd.Aconfigd.StorageRequestMessage; +import android.aconfigd.Aconfigd.StorageRequestMessages; +import android.aconfigd.Aconfigd.StorageReturnMessage; +import android.aconfigd.Aconfigd.StorageReturnMessages; +import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon; + +import java.io.DataInputStream; +import java.io.DataOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; @@ -185,6 +195,7 @@ public class SettingsToPropertiesMapper { "pmw", "power", "preload_safety", + "printing", "privacy_infra_policy", "resource_manager", "responsible_apis", @@ -224,6 +235,8 @@ public class SettingsToPropertiesMapper { public static final String NAMESPACE_REBOOT_STAGING = "staged"; public static final String NAMESPACE_REBOOT_STAGING_DELIMITER = "*"; + public static final String NAMESPACE_LOCAL_OVERRIDES = "device_config_overrides"; + private final String[] mGlobalSettings; private final String[] mDeviceConfigScopes; @@ -329,6 +342,7 @@ public class SettingsToPropertiesMapper { HashMap<String, HashMap<String, String>> propsToStage = getStagedFlagsWithValueChange(properties); + // send prop stage request to sys prop for (HashMap.Entry<String, HashMap<String, String>> entry : propsToStage.entrySet()) { String actualNamespace = entry.getKey(); HashMap<String, String> flagValuesToStage = entry.getValue(); @@ -349,7 +363,118 @@ public class SettingsToPropertiesMapper { } } - }); + // send prop stage request to new storage + if (enableAconfigStorageDaemon()) { + stageFlagsInNewStorage(propsToStage); + } + + }); + + // add prop sync callback for flag local overrides + DeviceConfig.addOnPropertiesChangedListener( + NAMESPACE_LOCAL_OVERRIDES, + AsyncTask.THREAD_POOL_EXECUTOR, + (DeviceConfig.Properties properties) -> { + if (enableAconfigStorageDaemon()) { + setLocalOverridesInNewStorage(properties); + } + }); + } + + /** + * apply flag local override in aconfig new storage + * @param props + * @return aconfigd socket return + */ + public static StorageReturnMessages sendAconfigdRequests(StorageRequestMessages requests) { + // connect to aconfigd socket + LocalSocket client = new LocalSocket(); + try{ + client.connect(new LocalSocketAddress( + "aconfigd", LocalSocketAddress.Namespace.RESERVED)); + log("connected to aconfigd socket"); + } catch (IOException ioe) { + log("failed to connect to aconfigd socket", ioe); + return null; + } + + DataInputStream inputStream = null; + DataOutputStream outputStream = null; + try { + inputStream = new DataInputStream(client.getInputStream()); + outputStream = new DataOutputStream(client.getOutputStream()); + } catch (IOException ioe) { + log("failed to get local socket iostreams", ioe); + return null; + } + + // send requests + try { + byte[] requests_bytes = requests.toByteArray(); + outputStream.writeInt(requests_bytes.length); + outputStream.write(requests_bytes, 0, requests_bytes.length); + log(requests.getMsgsCount() + " flag override requests sent to aconfigd"); + } catch (IOException ioe) { + log("failed to send requests to aconfigd", ioe); + return null; + } + + // read return + StorageReturnMessages return_msgs = null; + try { + int num_bytes = inputStream.readInt(); + byte[] buffer = new byte[num_bytes]; + inputStream.read(buffer, 0, num_bytes); + return_msgs = StorageReturnMessages.parseFrom(buffer); + log(return_msgs.getMsgsCount() + " acknowledgement received from aconfigd"); + } catch (IOException ioe) { + log("failed to read requests return from aconfigd", ioe); + return null; + } + + return return_msgs; + } + + /** + * apply flag local override in aconfig new storage + * @param props + */ + public static void setLocalOverridesInNewStorage(DeviceConfig.Properties props) { + StorageRequestMessages.Builder requests_builder = StorageRequestMessages.newBuilder(); + for (String flagName : props.getKeyset()) { + String flagValue = props.getString(flagName, null); + if (flagName == null || flagValue == null) { + continue; + } + + int idx = flagName.indexOf(":"); + if (idx == -1 || idx == flagName.length() - 1 || idx == 0) { + log("invalid local flag override: " + flagName); + continue; + } + String actualNamespace = flagName.substring(0, idx); + String fullFlagName = flagName.substring(idx+1); + idx = fullFlagName.lastIndexOf("."); + if (idx == -1) { + log("invalid flag name: " + fullFlagName); + continue; + } + String packageName = fullFlagName.substring(0, idx); + String realFlagName = fullFlagName.substring(idx+1); + + StorageRequestMessage.FlagOverrideMessage.Builder override_msg_builder = + StorageRequestMessage.FlagOverrideMessage.newBuilder(); + override_msg_builder.setPackageName(packageName); + override_msg_builder.setFlagName(realFlagName); + override_msg_builder.setFlagValue(flagValue); + override_msg_builder.setIsLocal(true); + + StorageRequestMessage.Builder request_builder = StorageRequestMessage.newBuilder(); + request_builder.setFlagOverrideMessage(override_msg_builder.build()); + requests_builder.addMsgs(request_builder.build()); + } + StorageRequestMessages requests = requests_builder.build(); + StorageReturnMessages acks = sendAconfigdRequests(requests); } public static SettingsToPropertiesMapper start(ContentResolver contentResolver) { @@ -421,6 +546,43 @@ public class SettingsToPropertiesMapper { } /** + * stage flags in aconfig new storage + * @param propsToStage + */ + @VisibleForTesting + static void stageFlagsInNewStorage(HashMap<String, HashMap<String, String>> propsToStage) { + // create storage request proto + StorageRequestMessages.Builder requests_builder = StorageRequestMessages.newBuilder(); + for (HashMap.Entry<String, HashMap<String, String>> entry : propsToStage.entrySet()) { + String actualNamespace = entry.getKey(); + HashMap<String, String> flagValuesToStage = entry.getValue(); + for (String fullFlagName : flagValuesToStage.keySet()) { + String stagedValue = flagValuesToStage.get(fullFlagName); + int idx = fullFlagName.lastIndexOf("."); + if (idx == -1) { + log("invalid flag name: " + fullFlagName); + continue; + } + String packageName = fullFlagName.substring(0, idx); + String flagName = fullFlagName.substring(idx+1); + + StorageRequestMessage.FlagOverrideMessage.Builder override_msg_builder = + StorageRequestMessage.FlagOverrideMessage.newBuilder(); + override_msg_builder.setPackageName(packageName); + override_msg_builder.setFlagName(flagName); + override_msg_builder.setFlagValue(stagedValue); + override_msg_builder.setIsLocal(false); + + StorageRequestMessage.Builder request_builder = StorageRequestMessage.newBuilder(); + request_builder.setFlagOverrideMessage(override_msg_builder.build()); + requests_builder.addMsgs(request_builder.build()); + } + } + StorageRequestMessages requests = requests_builder.build(); + StorageReturnMessages acks = sendAconfigdRequests(requests); + } + + /** * system property name constructing rule for aconfig flags: * "persist.device_config.aconfig_flags.[category_name].[flag_name]". * If the name contains invalid characters or substrings for system property name, @@ -483,10 +645,10 @@ public class SettingsToPropertiesMapper { for (String flagName : flagStagedValues.keySet()) { String stagedValue = flagStagedValues.get(flagName); String currentValue = flagCurrentValues.get(flagName); - if (currentValue == null) { - currentValue = new String("false"); + if (stagedValue == null) { + continue; } - if (stagedValue != null && !stagedValue.equalsIgnoreCase(currentValue)) { + if (currentValue == null || !stagedValue.equalsIgnoreCase(currentValue)) { flagsToStage.put(flagName, stagedValue); } } diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index fb63ec619918..b7108dfcbac3 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -116,3 +116,10 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "skip_unimportant_connections" + namespace: "backstage_power" + description: "Avoid OomAdjuster calculations for connections that won't change importance" + bug: "323376416" +} diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 63086525711a..ad93f6fc8d9f 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -55,6 +55,7 @@ import static android.app.AppOpsManager.SAMPLING_STRATEGY_RARELY_USED; import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM; import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM_OPS; import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE; +import static android.app.AppOpsManager.UID_STATE_NONEXISTENT; import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES; import static android.app.AppOpsManager._NUM_OP; import static android.app.AppOpsManager.extractFlagsFromKey; @@ -70,7 +71,6 @@ import static android.content.Intent.ACTION_PACKAGE_REMOVED; import static android.content.Intent.EXTRA_REPLACING; import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP; -import static android.permission.flags.Flags.runtimePermissionAppopsMappingEnabled; import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS; @@ -130,6 +130,7 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.storage.StorageManagerInternal; import android.permission.PermissionManager; +import android.permission.flags.Flags; import android.provider.Settings; import android.util.ArrayMap; import android.util.ArraySet; @@ -140,6 +141,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; +import android.util.SparseLongArray; import android.util.TimeUtils; import android.util.Xml; @@ -153,7 +155,6 @@ import com.android.internal.app.IAppOpsNotedCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.app.IAppOpsStartedCallback; import com.android.internal.app.MessageSamplingConfig; -import com.android.internal.camera.flags.Flags; import com.android.internal.compat.IPlatformCompat; import com.android.internal.os.Clock; import com.android.internal.pm.pkg.component.ParsedAttribution; @@ -1255,7 +1256,9 @@ public class AppOpsService extends IAppOpsService.Stub { for (int uidIdx = mUidStates.size() - 1; uidIdx >= 0; uidIdx--) { int uid = mUidStates.keyAt(uidIdx); if (knownUids.get(uid, false)) { - if (uid >= Process.FIRST_APPLICATION_UID) { + int appId = UserHandle.getAppId(uid); + if (appId >= Process.FIRST_APPLICATION_UID + && appId <= Process.LAST_APPLICATION_UID) { ArrayMap<String, Ops> pkgOps = mUidStates.valueAt(uidIdx).pkgOps; for (int pkgIdx = pkgOps.size() - 1; pkgIdx >= 0; pkgIdx--) { String pkgName = pkgOps.keyAt(pkgIdx); @@ -1419,6 +1422,9 @@ public class AppOpsService extends IAppOpsService.Stub { // The callback method from AppOpsUidStateTracker private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) { synchronized (this) { + if (state == UID_STATE_NONEXISTENT) { + onUidProcessDeathLocked(uid); + } UidState uidState = getUidStateLocked(uid, false); boolean hasForegroundWatchers = false; @@ -1506,6 +1512,11 @@ public class AppOpsService extends IAppOpsService.Stub { } } + if (state == UID_STATE_NONEXISTENT) { + // For UID_STATE_NONEXISTENT, we don't call onUidStateChanged for AttributedOps + return; + } + if (uidState != null) { int numPkgs = uidState.pkgOps.size(); for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) { @@ -1530,6 +1541,81 @@ public class AppOpsService extends IAppOpsService.Stub { } } + @GuardedBy("this") + private void onUidProcessDeathLocked(int uid) { + if (!mUidStates.contains(uid) || !Flags.finishRunningOpsForKilledPackages()) { + return; + } + final SparseLongArray chainsToFinish = new SparseLongArray(); + doForAllAttributedOpsInUidLocked(uid, (attributedOp) -> { + attributedOp.doForAllInProgressStartOpEvents((event) -> { + int chainId = event.getAttributionChainId(); + if (chainId != ATTRIBUTION_CHAIN_ID_NONE) { + long currentEarliestStartTime = + chainsToFinish.get(chainId, Long.MAX_VALUE); + if (event.getStartTime() < currentEarliestStartTime) { + // Store the earliest chain link we're finishing, so that we can go back + // and finish any links in the chain that started after this one + chainsToFinish.put(chainId, event.getStartTime()); + } + } + attributedOp.finished(event.getClientId()); + }); + }); + finishChainsLocked(chainsToFinish); + } + + @GuardedBy("this") + private void finishChainsLocked(SparseLongArray chainsToFinish) { + doForAllAttributedOpsLocked((attributedOp) -> { + attributedOp.doForAllInProgressStartOpEvents((event) -> { + int chainId = event.getAttributionChainId(); + // If this event is part of a chain, and this event started after the event in the + // chain we already finished, then finish this event, too + long earliestEventStart = chainsToFinish.get(chainId, Long.MAX_VALUE); + if (chainId != ATTRIBUTION_CHAIN_ID_NONE + && event.getStartTime() >= earliestEventStart) { + attributedOp.finished(event.getClientId()); + } + }); + }); + } + + @GuardedBy("this") + private void doForAllAttributedOpsLocked(Consumer<AttributedOp> action) { + int numUids = mUidStates.size(); + for (int uidNum = 0; uidNum < numUids; uidNum++) { + int uid = mUidStates.keyAt(uidNum); + doForAllAttributedOpsInUidLocked(uid, action); + } + } + + @GuardedBy("this") + private void doForAllAttributedOpsInUidLocked(int uid, Consumer<AttributedOp> action) { + UidState uidState = mUidStates.get(uid); + if (uidState == null) { + return; + } + + int numPkgs = uidState.pkgOps.size(); + for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) { + Ops ops = uidState.pkgOps.valueAt(pkgNum); + int numOps = ops.size(); + for (int opNum = 0; opNum < numOps; opNum++) { + Op op = ops.valueAt(opNum); + int numDevices = op.mDeviceAttributedOps.size(); + for (int deviceNum = 0; deviceNum < numDevices; deviceNum++) { + ArrayMap<String, AttributedOp> attrOps = + op.mDeviceAttributedOps.valueAt(deviceNum); + int numAttributions = attrOps.size(); + for (int attrNum = 0; attrNum < numAttributions; attrNum++) { + action.accept(attrOps.valueAt(attrNum)); + } + } + } + } + } + /** * Notify the proc state or capability has changed for a certain UID. */ @@ -2700,7 +2786,7 @@ public class AppOpsService extends IAppOpsService.Stub { * have information on them. */ private static boolean isOpAllowedForUid(int uid) { - return runtimePermissionAppopsMappingEnabled() + return Flags.runtimePermissionAppopsMappingEnabled() && (uid == Process.ROOT_UID || uid == Process.SYSTEM_UID); } @@ -2910,10 +2996,12 @@ public class AppOpsService extends IAppOpsService.Stub { final int proxyUid = attributionSource.getUid(); final String proxyPackageName = attributionSource.getPackageName(); final String proxyAttributionTag = attributionSource.getAttributionTag(); - final int proxiedUid = attributionSource.getNextUid(); final int proxyVirtualDeviceId = attributionSource.getDeviceId(); + + final int proxiedUid = attributionSource.getNextUid(); final String proxiedPackageName = attributionSource.getNextPackageName(); final String proxiedAttributionTag = attributionSource.getNextAttributionTag(); + final int proxiedVirtualDeviceId = attributionSource.getNextDeviceId(); verifyIncomingProxyUid(attributionSource); verifyIncomingOp(code); @@ -2950,7 +3038,8 @@ public class AppOpsService extends IAppOpsService.Stub { final SyncNotedAppOp proxyReturn = noteOperationUnchecked(code, proxyUid, resolveProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId, - Process.INVALID_UID, null, null, proxyFlags, !isProxyTrusted, + Process.INVALID_UID, null, null, + Context.DEVICE_ID_DEFAULT, proxyFlags, !isProxyTrusted, "proxy " + message, shouldCollectMessage); if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) { return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag, @@ -2968,9 +3057,9 @@ public class AppOpsService extends IAppOpsService.Stub { final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED : AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED; return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName, - proxiedAttributionTag, proxyVirtualDeviceId, proxyUid, resolveProxyPackageName, - proxyAttributionTag, proxiedFlags, shouldCollectAsyncNotedOp, message, - shouldCollectMessage); + proxiedAttributionTag, proxiedVirtualDeviceId, proxyUid, resolveProxyPackageName, + proxyAttributionTag, proxyVirtualDeviceId, proxiedFlags, shouldCollectAsyncNotedOp, + message, shouldCollectMessage); } @Override @@ -3021,14 +3110,14 @@ public class AppOpsService extends IAppOpsService.Stub { } return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag, virtualDeviceId, Process.INVALID_UID, null, null, - AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp, message, - shouldCollectMessage); + Context.DEVICE_ID_DEFAULT, AppOpsManager.OP_FLAG_SELF, shouldCollectAsyncNotedOp, + message, shouldCollectMessage); } private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName, @Nullable String attributionTag, int virtualDeviceId, int proxyUid, - String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags, - boolean shouldCollectAsyncNotedOp, @Nullable String message, + String proxyPackageName, @Nullable String proxyAttributionTag, int proxyVirtualDeviceId, + @OpFlags int flags, boolean shouldCollectAsyncNotedOp, @Nullable String message, boolean shouldCollectMessage) { PackageVerificationResult pvr; try { @@ -3159,8 +3248,9 @@ public class AppOpsService extends IAppOpsService.Stub { } scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, virtualDeviceId, flags, AppOpsManager.MODE_ALLOWED); + attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag, - uidState.getState(), flags); + getPersistentId(proxyVirtualDeviceId), uidState.getState(), flags); if (shouldCollectAsyncNotedOp) { collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message, @@ -3526,9 +3616,9 @@ public class AppOpsService extends IAppOpsService.Stub { } return startOperationUnchecked(clientId, code, uid, packageName, attributionTag, - virtualDeviceId, Process.INVALID_UID, null, null, OP_FLAG_SELF, - startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, - attributionFlags, attributionChainId); + virtualDeviceId, Process.INVALID_UID, null, null, Context.DEVICE_ID_DEFAULT, + OP_FLAG_SELF, startIfModeDefault, shouldCollectAsyncNotedOp, message, + shouldCollectMessage, attributionFlags, attributionChainId); } /** @deprecated Use {@link #startProxyOperationWithState} instead. */ @@ -3566,18 +3656,32 @@ public class AppOpsService extends IAppOpsService.Stub { final int proxyUid = attributionSource.getUid(); final String proxyPackageName = attributionSource.getPackageName(); final String proxyAttributionTag = attributionSource.getAttributionTag(); - final int proxiedUid = attributionSource.getNextUid(); final int proxyVirtualDeviceId = attributionSource.getDeviceId(); + + final int proxiedUid = attributionSource.getNextUid(); final String proxiedPackageName = attributionSource.getNextPackageName(); final String proxiedAttributionTag = attributionSource.getNextAttributionTag(); + final int proxiedVirtualDeviceId = attributionSource.getNextDeviceId(); verifyIncomingProxyUid(attributionSource); verifyIncomingOp(code); if (!isValidVirtualDeviceId(proxyVirtualDeviceId)) { - Slog.w(TAG, "startProxyOperationImpl returned MODE_IGNORED as virtualDeviceId " - + proxyVirtualDeviceId + " is invalid"); - return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag, - proxiedPackageName); + Slog.w( + TAG, + "startProxyOperationImpl returned MODE_IGNORED as proxyVirtualDeviceId " + + proxyVirtualDeviceId + + " is invalid"); + return new SyncNotedAppOp( + AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag, proxiedPackageName); + } + if (!isValidVirtualDeviceId(proxiedVirtualDeviceId)) { + Slog.w( + TAG, + "startProxyOperationImpl returned MODE_IGNORED as proxiedVirtualDeviceId " + + proxiedVirtualDeviceId + + " is invalid"); + return new SyncNotedAppOp( + AppOpsManager.MODE_IGNORED, code, proxiedAttributionTag, proxiedPackageName); } if (!isIncomingPackageValid(proxyPackageName, UserHandle.getUserId(proxyUid)) || !isIncomingPackageValid(proxiedPackageName, UserHandle.getUserId(proxiedUid))) { @@ -3619,7 +3723,7 @@ public class AppOpsService extends IAppOpsService.Stub { // Test if the proxied operation will succeed before starting the proxy operation final SyncNotedAppOp testProxiedOp = startOperationDryRun(code, proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, - proxyVirtualDeviceId, resolvedProxyPackageName, proxiedFlags, + proxiedVirtualDeviceId, resolvedProxyPackageName, proxiedFlags, startIfModeDefault); if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) { @@ -3631,7 +3735,7 @@ public class AppOpsService extends IAppOpsService.Stub { final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName, proxyAttributionTag, proxyVirtualDeviceId, - Process.INVALID_UID, null, null, proxyFlags, + Process.INVALID_UID, null, null, Context.DEVICE_ID_DEFAULT, proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message, shouldCollectMessage, proxyAttributionFlags, attributionChainId); if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) { @@ -3640,9 +3744,10 @@ public class AppOpsService extends IAppOpsService.Stub { } return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName, - proxiedAttributionTag, proxyVirtualDeviceId, proxyUid, resolvedProxyPackageName, - proxyAttributionTag, proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, - message, shouldCollectMessage, proxiedAttributionFlags, attributionChainId); + proxiedAttributionTag, proxiedVirtualDeviceId, proxyUid, resolvedProxyPackageName, + proxyAttributionTag, proxyVirtualDeviceId, proxiedFlags, startIfModeDefault, + shouldCollectAsyncNotedOp, message, shouldCollectMessage, proxiedAttributionFlags, + attributionChainId); } private boolean shouldStartForMode(int mode, boolean startIfModeDefault) { @@ -3652,9 +3757,10 @@ public class AppOpsService extends IAppOpsService.Stub { private SyncNotedAppOp startOperationUnchecked(IBinder clientId, int code, int uid, @NonNull String packageName, @Nullable String attributionTag, int virtualDeviceId, int proxyUid, String proxyPackageName, @Nullable String proxyAttributionTag, - @OpFlags int flags, boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, - @Nullable String message, boolean shouldCollectMessage, - @AttributionFlags int attributionFlags, int attributionChainId) { + int proxyVirtualDeviceId, @OpFlags int flags, boolean startIfModeDefault, + boolean shouldCollectAsyncNotedOp, @Nullable String message, + boolean shouldCollectMessage, @AttributionFlags int attributionFlags, + int attributionChainId) { PackageVerificationResult pvr; try { pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName); @@ -3749,13 +3855,13 @@ public class AppOpsService extends IAppOpsService.Stub { + " flags: " + AppOpsManager.flagsToString(flags)); try { if (isRestricted) { - attributedOp.createPaused(clientId, proxyUid, proxyPackageName, - proxyAttributionTag, virtualDeviceId, uidState.getState(), flags, - attributionFlags, attributionChainId); + attributedOp.createPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName, + proxyAttributionTag, getPersistentId(proxyVirtualDeviceId), + uidState.getState(), flags, attributionFlags, attributionChainId); } else { - attributedOp.started(clientId, proxyUid, proxyPackageName, - proxyAttributionTag, virtualDeviceId, uidState.getState(), flags, - attributionFlags, attributionChainId); + attributedOp.started(clientId, virtualDeviceId, proxyUid, proxyPackageName, + proxyAttributionTag, getPersistentId(proxyVirtualDeviceId), + uidState.getState(), flags, attributionFlags, attributionChainId); startType = START_TYPE_STARTED; } } catch (RemoteException e) { @@ -4753,8 +4859,8 @@ public class AppOpsService extends IAppOpsService.Stub { if ((code == OP_CAMERA) && isAutomotive()) { final long identity = Binder.clearCallingIdentity(); try { - if ((Flags.cameraPrivacyAllowlist()) - && (mSensorPrivacyManager.isCameraPrivacyEnabled(packageName))) { + if (com.android.internal.camera.flags.Flags.cameraPrivacyAllowlist() + && mSensorPrivacyManager.isCameraPrivacyEnabled(packageName)) { return true; } } finally { @@ -4944,7 +5050,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (accessTime > 0) { attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg, - proxyAttributionTag, uidState, opFlags); + proxyAttributionTag, PERSISTENT_DEVICE_ID_DEFAULT, uidState, opFlags); } if (rejectTime > 0) { attributedOp.rejected(rejectTime, uidState, opFlags); diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java index 18ea8cfc1386..268b286d8fe1 100644 --- a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java +++ b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java @@ -68,6 +68,7 @@ interface AppOpsUidStateTracker { return UID_STATE_BACKGROUND; } + // UID_STATE_NONEXISTENT is deliberately excluded here return UID_STATE_CACHED; } diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java index bc6ef2005584..03c81560be89 100644 --- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java @@ -34,7 +34,9 @@ import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.app.AppOpsManager.OP_TAKE_AUDIO_FOCUS; import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE; import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED; +import static android.app.AppOpsManager.UID_STATE_NONEXISTENT; import static android.app.AppOpsManager.UID_STATE_TOP; +import static android.permission.flags.Flags.finishRunningOpsForKilledPackages; import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState; @@ -343,13 +345,14 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { int capability = mCapability.get(uid, PROCESS_CAPABILITY_NONE); boolean appWidgetVisible = mAppWidgetVisible.get(uid, false); + boolean foregroundChange = uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED + != pendingUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED + || capability != pendingCapability + || appWidgetVisible != pendingAppWidgetVisible; + if (uidState != pendingUidState || capability != pendingCapability || appWidgetVisible != pendingAppWidgetVisible) { - boolean foregroundChange = uidState <= UID_STATE_MAX_LAST_NON_RESTRICTED - != pendingUidState <= UID_STATE_MAX_LAST_NON_RESTRICTED - || capability != pendingCapability - || appWidgetVisible != pendingAppWidgetVisible; if (foregroundChange) { // To save on memory usage, log only interesting changes. @@ -372,6 +375,16 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker { mCapability.delete(uid); mAppWidgetVisible.delete(uid); mPendingGone.delete(uid); + if (finishRunningOpsForKilledPackages()) { + for (int i = 0; i < mUidStateChangedCallbacks.size(); i++) { + UidStateChangedCallback cb = mUidStateChangedCallbacks.keyAt(i); + Executor executor = mUidStateChangedCallbacks.valueAt(i); + + executor.execute(PooledLambda.obtainRunnable( + UidStateChangedCallback::onUidStateChanged, cb, uid, + UID_STATE_NONEXISTENT, foregroundChange)); + } + } } else { mUidStates.put(uid, pendingUidState); mCapability.put(uid, pendingCapability); diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java index 2285826c0b58..02fc9938c02c 100644 --- a/services/core/java/com/android/server/appop/AttributedOp.java +++ b/services/core/java/com/android/server/appop/AttributedOp.java @@ -24,7 +24,6 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; -import android.companion.virtual.VirtualDeviceManager; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; @@ -39,6 +38,7 @@ import com.android.internal.util.function.pooled.PooledLambda; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; +import java.util.function.Consumer; final class AttributedOp { private final @NonNull AppOpsService mAppOpsService; @@ -95,16 +95,17 @@ final class AttributedOp { * * @param proxyUid The uid of the proxy * @param proxyPackageName The package name of the proxy - * @param proxyAttributionTag the attributionTag in the proxies package + * @param proxyAttributionTag The attributionTag in the proxies package + * @param proxyDeviceId The device Id of the proxy * @param uidState UID state of the app noteOp/startOp was called for * @param flags OpFlags of the call */ public void accessed(int proxyUid, @Nullable String proxyPackageName, - @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState, - @AppOpsManager.OpFlags int flags) { + @Nullable String proxyAttributionTag, @Nullable String proxyDeviceId, + @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) { long accessTime = System.currentTimeMillis(); - accessed(accessTime, -1, proxyUid, proxyPackageName, - proxyAttributionTag, uidState, flags); + accessed(accessTime, -1, proxyUid, proxyPackageName, proxyAttributionTag, proxyDeviceId, + uidState, flags); mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, tag, uidState, flags, accessTime, @@ -118,14 +119,16 @@ final class AttributedOp { * @param duration The duration of the event * @param proxyUid The uid of the proxy * @param proxyPackageName The package name of the proxy - * @param proxyAttributionTag the attributionTag in the proxies package + * @param proxyAttributionTag The attributionTag in the proxies package + * @param proxyDeviceId The device Id of the proxy * @param uidState UID state of the app noteOp/startOp was called for * @param flags OpFlags of the call */ @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService public void accessed(long noteTime, long duration, int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags) { + @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState, + @AppOpsManager.OpFlags int flags) { long key = makeKey(uidState, flags); if (mAccessEvents == null) { @@ -135,7 +138,7 @@ final class AttributedOp { AppOpsManager.OpEventProxyInfo proxyInfo = null; if (proxyUid != Process.INVALID_UID) { proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName, - proxyAttributionTag, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); + proxyAttributionTag, proxyDeviceId); } AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key); @@ -189,35 +192,36 @@ final class AttributedOp { * Update state when start was called * * @param clientId Id of the startOp caller + * @param virtualDeviceId The virtual device id of the startOp caller * @param proxyUid The UID of the proxy app * @param proxyPackageName The package name of the proxy app * @param proxyAttributionTag The attribution tag of the proxy app + * @param proxyDeviceId The device id of the proxy app * @param uidState UID state of the app startOp is called for * @param flags The proxy flags * @param attributionFlags The attribution flags associated with this operation. - * @param attributionChainId The if of the attribution chain this operations is a part of. + * @param attributionChainId The if of the attribution chain this operations is a part of */ - public void started(@NonNull IBinder clientId, int proxyUid, + public void started(@NonNull IBinder clientId, int virtualDeviceId, int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState, + @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) throws RemoteException { - startedOrPaused(clientId, proxyUid, proxyPackageName, - proxyAttributionTag, proxyVirtualDeviceId, uidState, flags, - /* triggeredByUidStateChange */ false, /* isStarted */ true, attributionFlags, - attributionChainId); + startedOrPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName, proxyAttributionTag, + proxyDeviceId, uidState, flags, attributionFlags, attributionChainId, false, + true); } @SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService - private void startedOrPaused(@NonNull IBinder clientId, int proxyUid, + private void startedOrPaused(@NonNull IBinder clientId, int virtualDeviceId, int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState, - @AppOpsManager.OpFlags int flags, boolean triggeredByUidStateChange, - boolean isStarted, @AppOpsManager.AttributionFlags int attributionFlags, - int attributionChainId) throws RemoteException { + @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState, + @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags, + int attributionChainId, boolean triggeredByUidStateChange, boolean isStarted) + throws RemoteException { if (!triggeredByUidStateChange && !parent.isRunning() && isStarted) { mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, - parent.packageName, tag, proxyVirtualDeviceId, true, attributionFlags, + parent.packageName, tag, virtualDeviceId, true, attributionFlags, attributionChainId); } @@ -233,9 +237,9 @@ final class AttributedOp { InProgressStartOpEvent event = events.get(clientId); if (event == null) { event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime, - SystemClock.elapsedRealtime(), clientId, tag, proxyVirtualDeviceId, + SystemClock.elapsedRealtime(), clientId, tag, virtualDeviceId, PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId), - proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags, + proxyUid, proxyPackageName, proxyAttributionTag, proxyDeviceId, uidState, flags, attributionFlags, attributionChainId); events.put(clientId, event); } else { @@ -253,6 +257,19 @@ final class AttributedOp { } } + public void doForAllInProgressStartOpEvents(Consumer<InProgressStartOpEvent> action) { + ArrayMap<IBinder, AttributedOp.InProgressStartOpEvent> events = isPaused() + ? mPausedInProgressEvents : mInProgressEvents; + if (events == null) { + return; + } + + int numStartedOps = events.size(); + for (int i = 0; i < numStartedOps; i++) { + action.accept(events.valueAt(i)); + } + } + /** * Update state when finishOp was called. Will finish started ops, and delete paused ops. * @@ -366,15 +383,14 @@ final class AttributedOp { /** * Create an event that will be started, if the op is unpaused. */ - public void createPaused(@NonNull IBinder clientId, int proxyUid, - @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, - int proxyVirtualDeviceId, @AppOpsManager.UidState int uidState, - @AppOpsManager.OpFlags int flags, - @AppOpsManager.AttributionFlags int attributionFlags, + public void createPaused(@NonNull IBinder clientId, int virtualDeviceId, + int proxyUid, @Nullable String proxyPackageName, @Nullable String proxyAttributionTag, + @Nullable String proxyDeviceId, @AppOpsManager.UidState int uidState, + @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) throws RemoteException { - startedOrPaused(clientId, proxyUid, proxyPackageName, proxyAttributionTag, - proxyVirtualDeviceId, uidState, flags, false, false, - attributionFlags, attributionChainId); + startedOrPaused(clientId, virtualDeviceId, proxyUid, proxyPackageName, proxyAttributionTag, + proxyDeviceId, uidState, flags, attributionFlags, attributionChainId, false, + false); } /** @@ -496,16 +512,16 @@ final class AttributedOp { // Call started() to add a new start event object and then add the // previously removed unfinished start counts back if (proxy != null) { - startedOrPaused(event.getClientId(), proxy.getUid(), - proxy.getPackageName(), proxy.getAttributionTag(), - event.getVirtualDeviceId(), newState, event.getFlags(), - true, isRunning, - event.getAttributionFlags(), event.getAttributionChainId()); + startedOrPaused(event.getClientId(), event.getVirtualDeviceId(), + proxy.getUid(), proxy.getPackageName(), proxy.getAttributionTag(), + proxy.getDeviceId(), newState, event.getFlags(), + event.getAttributionFlags(), event.getAttributionChainId(), true, + isRunning); } else { - startedOrPaused(event.getClientId(), Process.INVALID_UID, null, null, - event.getVirtualDeviceId(), newState, event.getFlags(), true, - isRunning, event.getAttributionFlags(), - event.getAttributionChainId()); + startedOrPaused(event.getClientId(), event.getVirtualDeviceId(), + Process.INVALID_UID, null, null, null, + newState, event.getFlags(), event.getAttributionFlags(), + event.getAttributionChainId(), true, isRunning); } events = isRunning ? mInProgressEvents : mPausedInProgressEvents; @@ -847,7 +863,8 @@ final class AttributedOp { InProgressStartOpEvent acquire(long startTime, long elapsedTime, @NonNull IBinder clientId, @Nullable String attributionTag, int virtualDeviceId, @NonNull Runnable onDeath, int proxyUid, @Nullable String proxyPackageName, - @Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState, + @Nullable String proxyAttributionTag, @Nullable String proxyDeviceId, + @AppOpsManager.UidState int uidState, @AppOpsManager.OpFlags int flags, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId) throws RemoteException { @@ -856,7 +873,7 @@ final class AttributedOp { AppOpsManager.OpEventProxyInfo proxyInfo = null; if (proxyUid != Process.INVALID_UID) { proxyInfo = mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName, - proxyAttributionTag, VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); + proxyAttributionTag, proxyDeviceId); } if (recycled != null) { @@ -880,7 +897,8 @@ final class AttributedOp { super(maxUnusedPooledObjects); } - AppOpsManager.OpEventProxyInfo acquire(@IntRange(from = 0) int uid, + AppOpsManager.OpEventProxyInfo acquire( + @IntRange(from = 0) int uid, @Nullable String packageName, @Nullable String attributionTag, @Nullable String deviceId) { @@ -890,7 +908,7 @@ final class AttributedOp { return recycled; } - return new AppOpsManager.OpEventProxyInfo(uid, packageName, attributionTag); + return new AppOpsManager.OpEventProxyInfo(uid, packageName, attributionTag, deviceId); } } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index add84910bf48..15c5c1077803 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -4440,7 +4440,8 @@ public class AudioService extends IAudioService.Stub || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING) { voiceActive = true; } - if (usage == AudioAttributes.USAGE_MEDIA || usage == AudioAttributes.USAGE_GAME) { + if (usage == AudioAttributes.USAGE_MEDIA || usage == AudioAttributes.USAGE_GAME + || usage == AudioAttributes.USAGE_UNKNOWN) { mediaActive = true; } } @@ -9778,9 +9779,9 @@ public class AudioService extends IAudioService.Stub mContentResolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.DOCK_AUDIO_MEDIA_ENABLED), false, this); mContentResolver.registerContentObserver(Settings.System.getUriFor( - Settings.System.MASTER_MONO), false, this); + Settings.System.MASTER_MONO), false, this, UserHandle.USER_ALL); mContentResolver.registerContentObserver(Settings.System.getUriFor( - Settings.System.MASTER_BALANCE), false, this); + Settings.System.MASTER_BALANCE), false, this, UserHandle.USER_ALL); mEncodedSurroundMode = mSettings.getGlobalInt( mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT, diff --git a/services/core/java/com/android/server/audio/MusicFxHelper.java b/services/core/java/com/android/server/audio/MusicFxHelper.java index ba453101046f..cf0b2ae15618 100644 --- a/services/core/java/com/android/server/audio/MusicFxHelper.java +++ b/services/core/java/com/android/server/audio/MusicFxHelper.java @@ -70,6 +70,8 @@ public class MusicFxHelper { // The binder token identifying the UidObserver registration. private IBinder mUidObserverToken = null; + private boolean mIsBound; + // Package name and list of open audio sessions for this package private static class PackageSessions { String mPackageName; @@ -109,6 +111,7 @@ public class MusicFxHelper { if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { Intent bindIntent = new Intent().setClassName(mMusicFxPackageName, "com.android.musicfx.KeepAliveService"); + mIsBound = true; mContext.bindServiceAsUser( bindIntent, mMusicFxBindConnection, Context.BIND_AUTO_CREATE, UserHandle.of(getCurrentUserId())); @@ -157,9 +160,12 @@ public class MusicFxHelper { Log.e(TAG, "RemoteException with unregisterUidObserver: " + e); } mUidObserverToken = null; - mContext.unbindService(mMusicFxBindConnection); - Log.i(TAG, "last session closed, unregister UID observer, and unbind " - + mMusicFxPackageName); + if (mIsBound) { + mContext.unbindService(mMusicFxBindConnection); + mIsBound = false; + Log.i(TAG, "last session closed, unregister UID observer, and unbind " + + mMusicFxPackageName); + } } } } diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index e2c4b4638207..cae169550d9a 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -347,9 +347,6 @@ public class SpatializerHelper { //------------------------------------------------------ // routing monitoring synchronized void onRoutingUpdated() { - if (!mFeatureEnabled) { - return; - } switch (mState) { case STATE_UNINITIALIZED: case STATE_NOT_SUPPORTED: @@ -393,7 +390,7 @@ public class SpatializerHelper { setDispatchAvailableState(false); } - boolean enabled = able && enabledAvailable.first; + boolean enabled = mFeatureEnabled && able && enabledAvailable.first; if (enabled) { loglogi("Enabling Spatial Audio since enabled for media device:" + currentDevice); diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index 11cca66318d6..2a1687209aad 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -298,7 +298,7 @@ public class AuthService extends SystemService { return -1; } - if (promptInfo.containsTestConfigurations()) { + if (promptInfo.requiresTestOrInternalPermission()) { if (getContext().checkCallingOrSelfPermission(TEST_BIOMETRIC) != PackageManager.PERMISSION_GRANTED) { checkInternalPermission(); @@ -306,10 +306,10 @@ public class AuthService extends SystemService { } // Only allow internal clients to enable non-public options. - if (promptInfo.containsPrivateApiConfigurations()) { + if (promptInfo.requiresInternalPermission()) { checkInternalPermission(); } - if (promptInfo.containsAdvancedApiConfigurations()) { + if (promptInfo.requiresAdvancedPermission()) { checkBiometricAdvancedPermission(); } diff --git a/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java b/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java index 3e8acee26e81..7cf2d3028aef 100644 --- a/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java +++ b/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java @@ -16,6 +16,7 @@ package com.android.server.biometrics; import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS; +import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; import android.annotation.NonNull; import android.content.BroadcastReceiver; @@ -63,7 +64,7 @@ public class BiometricDanglingReceiver extends BroadcastReceiver { intentFilter.addAction(ACTION_FACE_RE_ENROLL_LAUNCH); intentFilter.addAction(ACTION_FACE_RE_ENROLL_DISMISS); } - context.registerReceiver(this, intentFilter); + context.registerReceiver(this, intentFilter, Context.RECEIVER_NOT_EXPORTED); } @Override @@ -84,7 +85,8 @@ public class BiometricDanglingReceiver extends BroadcastReceiver { } private void launchBiometricEnrollActivity(Context context, String action) { - context.sendBroadcast(new Intent(ACTION_CLOSE_SYSTEM_DIALOGS)); + context.sendBroadcast( + new Intent(ACTION_CLOSE_SYSTEM_DIALOGS).setFlags(FLAG_RECEIVER_FOREGROUND)); final Intent intent = new Intent(action); intent.setPackage(SETTINGS_PACKAGE); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 645a3664681b..390ee96a3417 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -37,6 +37,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.res.Configuration; +import android.graphics.ImageFormat; import android.graphics.Rect; import android.hardware.CameraExtensionSessionStats; import android.hardware.CameraSessionStats; @@ -906,6 +907,7 @@ public class CameraServiceProxy extends SystemService int extensionType = FrameworkStatsLog.CAMERA_ACTION_EVENT__EXT_TYPE__EXTENSION_NONE; boolean extensionIsAdvanced = false; + int extensionCaptureFormat = ImageFormat.UNKNOWN; if (e.mExtSessionStats != null) { switch (e.mExtSessionStats.type) { case CameraExtensionSessionStats.Type.EXTENSION_AUTOMATIC: @@ -932,6 +934,9 @@ public class CameraServiceProxy extends SystemService Slog.w(TAG, "Unknown extension type: " + e.mExtSessionStats.type); } extensionIsAdvanced = e.mExtSessionStats.isAdvanced; + if (Flags.analytics24q3()) { + extensionCaptureFormat = e.mExtSessionStats.captureFormat; + } } int streamCount = 0; @@ -945,10 +950,13 @@ public class CameraServiceProxy extends SystemService String zoomOverrideDebug = Flags.logZoomOverrideUsage() ? ", zoomOverrideUsage " + e.mUsedZoomOverride : ""; - String mostRequestedFpsRangeDebug = Flags.analytics24q3() ? ", mostRequestedFpsRange " + e.mMostRequestedFpsRange : ""; + String extensionCaptureFormatDebug = Flags.analytics24q3() + ? " extensionCaptureFormat " + e.mExtSessionStats.captureFormat + : ""; + Slog.v(TAG, "CAMERA_ACTION_EVENT: action " + e.mAction + " clientName " + e.mClientName + ", duration " + e.getDuration() @@ -971,8 +979,10 @@ public class CameraServiceProxy extends SystemService + ", logId " + e.mLogId + ", sessionIndex " + e.mSessionIndex + ", mExtSessionStats {type " + extensionType - + " isAdvanced " + extensionIsAdvanced + "}"); + + " isAdvanced " + extensionIsAdvanced + + extensionCaptureFormatDebug + "}"); } + // Convert from CameraStreamStats to CameraStreamProto CameraStreamProto[] streamProtos = new CameraStreamProto[MAX_STREAM_STATISTICS]; for (int i = 0; i < MAX_STREAM_STATISTICS; i++) { @@ -1035,7 +1045,8 @@ public class CameraServiceProxy extends SystemService e.mLogId, e.mSessionIndex, extensionType, extensionIsAdvanced, e.mUsedUltraWide, e.mUsedZoomOverride, - e.mMostRequestedFpsRange.getLower(), e.mMostRequestedFpsRange.getUpper()); + e.mMostRequestedFpsRange.getLower(), e.mMostRequestedFpsRange.getUpper(), + extensionCaptureFormat); } } diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java index 22f3bbd80a77..fa8299bd45fd 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 nativeCreateDisplay(String name, boolean secure, - float requestedRefreshRate); + String uniqueId, float requestedRefreshRate); private static native void nativeDestroyDisplay(IBinder displayToken); private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes); private static native long[] nativeGetPhysicalDisplayIds(); @@ -43,20 +43,21 @@ public class DisplayControl { /** * Create a display in SurfaceFlinger. * - * @param name The name of the display + * @param name The name of the display. * @param secure Whether this display is secure. * @return The token reference for the display in SurfaceFlinger. */ public static IBinder createDisplay(String name, boolean secure) { Objects.requireNonNull(name, "name must not be null"); - return nativeCreateDisplay(name, secure, 0.0f); + return nativeCreateDisplay(name, secure, "", 0.0f); } /** * Create a display in SurfaceFlinger. * - * @param name The name of the display + * @param name The name of the display. * @param secure Whether this display is secure. + * @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 * 120hz display. If an arbitrary refresh rate is specified, the rate will be rounded @@ -65,9 +66,10 @@ public class DisplayControl { * @return The token reference for the display in SurfaceFlinger. */ public static IBinder createDisplay(String name, boolean secure, - float requestedRefreshRate) { + String uniqueId, float requestedRefreshRate) { Objects.requireNonNull(name, "name must not be null"); - return nativeCreateDisplay(name, secure, requestedRefreshRate); + Objects.requireNonNull(uniqueId, "uniqueId must not be null"); + return nativeCreateDisplay(name, secure, uniqueId, requestedRefreshRate); } /** @@ -79,7 +81,6 @@ public class DisplayControl { if (displayToken == null) { throw new IllegalArgumentException("displayToken must not be null"); } - nativeDestroyDisplay(displayToken); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index d7a7dd493903..70a1014a0c8c 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -1442,6 +1442,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // If there's an offload session, we need to set the initial doze brightness before // the offload session starts controlling the brightness. + // During the transition DOZE_SUSPEND -> DOZE -> DOZE_SUSPEND, this brightness strategy + // will be selected again, meaning that no new brightness will be sent to the hardware and + // the display will stay at the brightness level set by the offload session. if (Float.isNaN(brightnessState) && mFlags.isDisplayOffloadEnabled() && Display.isDozeState(state) && mDisplayOffloadSession != null) { if (mAutomaticBrightnessController != null @@ -1459,6 +1462,15 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (BrightnessUtils.isValidBrightnessValue(rawBrightnessState)) { brightnessState = clampScreenBrightness(rawBrightnessState); mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_INITIAL); + + if (mAutomaticBrightnessController != null + && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) { + // Keep the brightness in the setting so that we can use it after the screen + // turns on, until a lux sample becomes available. We don't do this when + // auto-brightness is disabled - in that situation we still want to use + // the last brightness from when the screen was on. + updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState; + } } } diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index bcdb442c3ad3..a29e8523952d 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -92,8 +92,9 @@ 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, float requestedRefreshRate) { - return DisplayControl.createDisplay(name, secure, requestedRefreshRate); + public IBinder createDisplay(String name, boolean secure, String uniqueId, + float requestedRefreshRate) { + return DisplayControl.createDisplay(name, secure, uniqueId, requestedRefreshRate); } @Override @@ -126,7 +127,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter { String name = virtualDisplayConfig.getName(); boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0; - IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure, + IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure, uniqueId, virtualDisplayConfig.getRequestedRefreshRate()); MediaProjectionCallback mediaProjectionCallback = null; if (projection != null) { @@ -653,8 +654,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter { /** * Create a virtual display in SurfaceFlinger. * - * @param name The name of the display + * @param name The name of the display. * @param secure Whether this display is secure. + * @param uniqueId The unique ID for the display. * @param requestedRefreshRate * The refresh rate, frames per second, to request on the virtual display. * It should be a divisor of refresh rate of the leader physical display @@ -663,8 +665,9 @@ 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, float requestedRefreshRate); - + IBinder createDisplay(String name, boolean secure, String uniqueId, + float requestedRefreshRate); + /** * Destroy a display in SurfaceFlinger. * diff --git a/services/core/java/com/android/server/hdmi/SendKeyAction.java b/services/core/java/com/android/server/hdmi/SendKeyAction.java index 2703a2c01848..7e18d8412aae 100644 --- a/services/core/java/com/android/server/hdmi/SendKeyAction.java +++ b/services/core/java/com/android/server/hdmi/SendKeyAction.java @@ -158,9 +158,11 @@ final class SendKeyAction extends HdmiCecFeatureAction { mTargetAddress, cecKeycodeAndParams), new SendMessageCallback() { @Override public void onSendCompleted(int error) { - if (error != SendMessageResult.SUCCESS) { + // Disable System Audio Mode, if the AVR doesn't acknowledge + // a <User Control Pressed> message. + if (error == SendMessageResult.NACK) { HdmiLogger.debug( - "AVR did not respond to <User Control Pressed>"); + "AVR did not acknowledge <User Control Pressed>"); localDevice().mService.setSystemAudioActivated(false); } } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index cbd309e1f957..83179914c746 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -156,7 +156,6 @@ public class InputManagerService extends IInputManager.Stub private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1; private static final int MSG_RELOAD_DEVICE_ALIASES = 2; private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 3; - private static final int MSG_POINTER_DISPLAY_ID_CHANGED = 4; private static final int DEFAULT_VIBRATION_MAGNITUDE = 192; private static final AdditionalDisplayInputProperties @@ -255,7 +254,7 @@ public class InputManagerService extends IInputManager.Stub // to {DisplayInfo#uniqueId} (String) so that events from the Input Device go to a // specific display. @GuardedBy("mAssociationsLock") - private final Map<String, String> mUniqueIdAssociations = new ArrayMap<>(); + private final Map<String, String> mUniqueIdAssociationsByPort = new ArrayMap<>(); // The associations of input devices to displays by descriptor. Maps from // {InputDevice#mDescriptor} to {DisplayInfo#uniqueId} (String) so that events from the @@ -1324,11 +1323,6 @@ public class InputManagerService extends IInputManager.Stub properties -> properties.pointerIconVisible = visible); } - private void handlePointerDisplayIdChanged(PointerDisplayIdChangedArgs args) { - mWindowManagerCallbacks.notifyPointerDisplayIdChanged( - args.mPointerDisplayId, args.mXPosition, args.mYPosition); - } - private void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible) { mNative.setDisplayEligibilityForPointerCapture(displayId, isEligible); } @@ -1612,18 +1606,6 @@ public class InputManagerService extends IInputManager.Stub // Binder call @Override - public void setPointerIconType(int iconType) { - // TODO(b/311416205): Remove. - } - - // Binder call - @Override - public void setCustomPointerIcon(PointerIcon icon) { - // TODO(b/311416205): Remove. - } - - // Binder call - @Override public boolean setPointerIcon(PointerIcon icon, int displayId, int deviceId, int pointerId, IBinder inputToken) { Objects.requireNonNull(icon); @@ -1674,7 +1656,8 @@ public class InputManagerService extends IInputManager.Stub } @Override // Binder call - public void addUniqueIdAssociation(@NonNull String inputPort, @NonNull String displayUniqueId) { + public void addUniqueIdAssociationByPort(@NonNull String inputPort, + @NonNull String displayUniqueId) { if (!checkCallingPermission( android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY, "addUniqueIdAssociation()")) { @@ -1685,13 +1668,13 @@ public class InputManagerService extends IInputManager.Stub Objects.requireNonNull(inputPort); Objects.requireNonNull(displayUniqueId); synchronized (mAssociationsLock) { - mUniqueIdAssociations.put(inputPort, displayUniqueId); + mUniqueIdAssociationsByPort.put(inputPort, displayUniqueId); } mNative.changeUniqueIdAssociation(); } @Override // Binder call - public void removeUniqueIdAssociation(@NonNull String inputPort) { + public void removeUniqueIdAssociationByPort(@NonNull String inputPort) { if (!checkCallingPermission( android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY, "removeUniqueIdAssociation()")) { @@ -1700,7 +1683,7 @@ public class InputManagerService extends IInputManager.Stub Objects.requireNonNull(inputPort); synchronized (mAssociationsLock) { - mUniqueIdAssociations.remove(inputPort); + mUniqueIdAssociationsByPort.remove(inputPort); } mNative.changeUniqueIdAssociation(); } @@ -2119,9 +2102,9 @@ public class InputManagerService extends IInputManager.Stub pw.println(" display: " + v); }); } - if (!mUniqueIdAssociations.isEmpty()) { + if (!mUniqueIdAssociationsByPort.isEmpty()) { pw.println("Unique Id Associations:"); - mUniqueIdAssociations.forEach((k, v) -> { + mUniqueIdAssociationsByPort.forEach((k, v) -> { pw.print(" port: " + k); pw.println(" uniqueId: " + v); }); @@ -2548,10 +2531,10 @@ public class InputManagerService extends IInputManager.Stub // Native callback @SuppressWarnings("unused") - private String[] getInputUniqueIdAssociations() { + private String[] getInputUniqueIdAssociationsByPort() { final Map<String, String> associations; synchronized (mAssociationsLock) { - associations = new HashMap<>(mUniqueIdAssociations); + associations = new HashMap<>(mUniqueIdAssociationsByPort); } return flatten(associations); @@ -2703,9 +2686,7 @@ public class InputManagerService extends IInputManager.Stub @SuppressWarnings("unused") @VisibleForTesting void onPointerDisplayIdChanged(int pointerDisplayId, float xPosition, float yPosition) { - mHandler.obtainMessage(MSG_POINTER_DISPLAY_ID_CHANGED, - new PointerDisplayIdChangedArgs(pointerDisplayId, xPosition, - yPosition)).sendToTarget(); + // TODO(b/311416205): Remove. } @Override @@ -2860,14 +2841,6 @@ public class InputManagerService extends IInputManager.Stub */ @Nullable SurfaceControl createSurfaceForGestureMonitor(String name, int displayId); - - /** - * Notify WindowManagerService when the display of the mouse pointer changes. - * @param displayId The display on which the mouse pointer is shown. - * @param x The x coordinate of the mouse pointer. - * @param y The y coordinate of the mouse pointer. - */ - void notifyPointerDisplayIdChanged(int displayId, float x, float y); } /** @@ -2911,9 +2884,6 @@ public class InputManagerService extends IInputManager.Stub boolean inTabletMode = (boolean) args.arg1; deliverTabletModeChanged(whenNanos, inTabletMode); break; - case MSG_POINTER_DISPLAY_ID_CHANGED: - handlePointerDisplayIdChanged((PointerDisplayIdChangedArgs) msg.obj); - break; } } } diff --git a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java index 6eae9a4bbe22..d7d57df7f74c 100644 --- a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java +++ b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java @@ -240,7 +240,8 @@ public class FocusEventDebugView extends RelativeLayout { return; } - post(() -> handleRotaryInput(MotionEvent.obtain((MotionEvent) event))); + MotionEvent motionEvent = MotionEvent.obtain(event); + post(() -> handleRotaryInput(motionEvent)); } private void handleKeyEvent(KeyEvent keyEvent) { diff --git a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java index 035a7485fe86..00bc7517bebd 100644 --- a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java +++ b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java @@ -40,6 +40,13 @@ final class AutofillSuggestionsController { @NonNull private final InputMethodManagerService mService; + /** + * The host input token of the input method that is currently associated with this controller. + */ + @GuardedBy("ImfLock.class") + @Nullable + private IBinder mCurHostInputToken; + private static final class CreateInlineSuggestionsRequest { @NonNull final InlineSuggestionsRequestInfo mRequestInfo; @NonNull final IInlineSuggestionsRequestCallback mCallback; @@ -78,6 +85,17 @@ final class AutofillSuggestionsController { } @GuardedBy("ImfLock.class") + void onResetSystemUi() { + mCurHostInputToken = null; + } + + @Nullable + @GuardedBy("ImfLock.class") + IBinder getCurHostInputToken() { + return mCurHostInputToken; + } + + @GuardedBy("ImfLock.class") void onCreateInlineSuggestionsRequest(@UserIdInt int userId, InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback, boolean touchExplorationEnabled) { @@ -124,8 +142,7 @@ final class AutofillSuggestionsController { mPendingInlineSuggestionsRequest.mCallback, mPendingInlineSuggestionsRequest.mPackageName, mService.getCurTokenDisplayIdLocked(), - mService.getCurTokenLocked(), - mService); + mService.getCurTokenLocked()); curMethod.onCreateInlineSuggestionsRequest( mPendingInlineSuggestionsRequest.mRequestInfo, callback); } else { @@ -161,22 +178,20 @@ final class AutofillSuggestionsController { * The decorator which validates the host package name in the * {@link InlineSuggestionsRequest} argument to make sure it matches the IME package name. */ - private static final class InlineSuggestionsRequestCallbackDecorator + private final class InlineSuggestionsRequestCallbackDecorator extends IInlineSuggestionsRequestCallback.Stub { @NonNull private final IInlineSuggestionsRequestCallback mCallback; @NonNull private final String mImePackageName; private final int mImeDisplayId; @NonNull private final IBinder mImeToken; - @NonNull private final InputMethodManagerService mImms; InlineSuggestionsRequestCallbackDecorator( @NonNull IInlineSuggestionsRequestCallback callback, @NonNull String imePackageName, - int displayId, @NonNull IBinder imeToken, @NonNull InputMethodManagerService imms) { + int displayId, @NonNull IBinder imeToken) { mCallback = callback; mImePackageName = imePackageName; mImeDisplayId = displayId; mImeToken = imeToken; - mImms = imms; } @Override @@ -195,7 +210,12 @@ final class AutofillSuggestionsController { + "]."); } request.setHostDisplayId(mImeDisplayId); - mImms.setCurHostInputToken(mImeToken, request.getHostInputToken()); + synchronized (ImfLock.class) { + final IBinder curImeToken = mService.getCurTokenLocked(); + if (mImeToken == curImeToken) { + mCurHostInputToken = request.getHostInputToken(); + } + } mCallback.onInlineSuggestionsRequest(request, callback); } diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java index 7956e03f22a9..79f1a9c90f53 100644 --- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java +++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java @@ -330,14 +330,10 @@ final class HandwritingModeController { mHandwritingSurface.startIntercepting(imePid, imeUid); // Unset the pointer icon for the stylus in case the app had set it. - if (com.android.input.flags.Flags.enablePointerChoreographer()) { - Objects.requireNonNull(mContext.getSystemService(InputManager.class)).setPointerIcon( - PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED), - downEvent.getDisplayId(), downEvent.getDeviceId(), downEvent.getPointerId(0), - mHandwritingSurface.getInputChannel().getToken()); - } else { - InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED); - } + Objects.requireNonNull(mContext.getSystemService(InputManager.class)).setPointerIcon( + PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED), + downEvent.getDisplayId(), downEvent.getDeviceId(), downEvent.getPointerId(0), + mHandwritingSurface.getInputChannel().getToken()); return new HandwritingSession(mCurrentRequestId, mHandwritingSurface.getInputChannel(), mHandwritingBuffer); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index 1d048cb687cb..e8543f225ef0 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -150,10 +150,11 @@ public abstract class InputMethodManagerInternal { * * @param sourceInputToken the source token. * @param displayId the display hosting the IME window + * @param userId the user ID this request is about * @return {@code true} if the transfer is successful */ public abstract boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken, - int displayId); + int displayId, @UserIdInt int userId); /** * Reports that IME control has transferred to the given window token, or if null that @@ -287,7 +288,7 @@ public abstract class InputMethodManagerInternal { @Override public boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken, - int displayId) { + int displayId, @UserIdInt int userId) { return false; } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 0fde760fd02a..67df99279242 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -485,26 +485,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return userData.mBindingController.getSelectedMethodId(); } - /** - * The current binding sequence number, incremented every time there is - * a new bind performed. - */ - @GuardedBy("ImfLock.class") - private int getSequenceNumberLocked() { - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - return userData.mBindingController.getSequenceNumber(); - } - - /** - * Increase the current binding sequence number by one. - * Reset to 1 on overflow. - */ - @GuardedBy("ImfLock.class") - private void advanceSequenceNumberLocked() { - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - userData.mBindingController.advanceSequenceNumber(); - } - @GuardedBy("ImfLock.class") @Nullable InputMethodInfo queryInputMethodForCurrentUserLocked(@NonNull String imeId) { @@ -583,16 +563,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private final WeakHashMap<IBinder, Boolean> mFocusedWindowPerceptible = new WeakHashMap<>(); /** - * Set to true if our ServiceConnection is currently actively bound to - * a service (whether or not we have gotten its IBinder back yet). - */ - @GuardedBy("ImfLock.class") - private boolean hasConnectionLocked() { - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - return userData.mBindingController.hasMainConnection(); - } - - /** * The token tracking the current IME show request that is waiting for a connection to an IME, * otherwise {@code null}. */ @@ -645,14 +615,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private int mCurTokenDisplayId = INVALID_DISPLAY; /** - * The host input token of the current active input method. - */ - @GuardedBy("ImfLock.class") - @Nullable - @MultiUserUnawareField - private IBinder mCurHostInputToken; - - /** * The display ID of the input method indicates the fallback display which returned by * {@link #computeImeDisplayIdForTarget}. */ @@ -679,16 +641,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } /** - * Time that we last initiated a bind to the input method, to determine - * if we should try to disconnect and reconnect to it. - */ - @GuardedBy("ImfLock.class") - private long getLastBindTimeLocked() { - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - return userData.mBindingController.getLastBindTime(); - } - - /** * Have we called mCurMethod.bindInput()? */ @MultiUserUnawareField @@ -1840,21 +1792,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } /** - * Sets current host input token. - * - * @param callerImeToken the token has been made for the current active input method - * @param hostInputToken the host input token of the current active input method - */ - void setCurHostInputToken(@NonNull IBinder callerImeToken, @Nullable IBinder hostInputToken) { - synchronized (ImfLock.class) { - if (!calledWithValidTokenLocked(callerImeToken)) { - return; - } - mCurHostInputToken = hostInputToken; - } - } - - /** * Gets enabled subtypes of the specified {@link InputMethodInfo}. * * @param imiId if null, returns enabled subtypes for the current {@link InputMethodInfo}. @@ -1968,7 +1905,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return mClientController.getClient(client.asBinder()); } - // TODO(b/314150112): Move this to ClientController. @GuardedBy("ImfLock.class") void unbindCurrentClientLocked(@UnbindReason int unbindClientReason) { if (mCurClient != null) { @@ -1988,7 +1924,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // all accessibility too. That means, when input method get disconnected (including // switching ime), we also unbind accessibility mCurClient.mClient.setActive(false /* active */, false /* fullscreen */); - mCurClient.mClient.onUnbindMethod(getSequenceNumberLocked(), unbindClientReason); + + // TODO(b/325515685): make binding controller user independent. Before this change, the + // following dependencies also need to be user independent: mCurClient, mBoundToMethod, + // getCurMethodLocked(), and mMenuController. + final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); + mCurClient.mClient.onUnbindMethod(userData.mBindingController.getSequenceNumber(), + unbindClientReason); mCurClient.mSessionRequested = false; mCurClient.mSessionRequestedForAccessibility = false; mCurClient = null; @@ -2066,12 +2008,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final boolean restarting = !initial; final Binder startInputToken = new Binder(); + final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); final StartInputInfo info = new StartInputInfo(mCurrentUserId, getCurTokenLocked(), mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting, UserHandle.getUserId(mCurClient.mUid), mCurClient.mSelfReportedDisplayId, mImeBindingState.mFocusedWindow, mCurEditorInfo, - mImeBindingState.mFocusedWindowSoftInputMode, getSequenceNumberLocked()); + mImeBindingState.mFocusedWindowSoftInputMode, + userData.mBindingController.getSequenceNumber()); mImeTargetWindowMap.put(startInputToken, mImeBindingState.mFocusedWindow); mStartInputHistory.addEntry(info); @@ -2104,21 +2048,20 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT); } - String curId = getCurIdLocked(); + final var curId = getCurIdLocked(); final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(mCurrentUserId) .getMethodMap().get(curId); final boolean suppressesSpellChecker = curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker(); final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions = createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions); - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); if (userData.mBindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) { mHwController.setInkWindowInitializer(new InkWindowInitializer()); } return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION, session.mSession, accessibilityInputMethodSessions, (session.mChannel != null ? session.mChannel.dup() : null), - curId, getSequenceNumberLocked(), suppressesSpellChecker); + curId, userData.mBindingController.getSequenceNumber(), suppressesSpellChecker); } @GuardedBy("ImfLock.class") @@ -2170,7 +2113,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @NonNull EditorInfo editorInfo, @StartInputFlags int startInputFlags, @StartInputReason int startInputReason, int unverifiedTargetSdkVersion, - @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { + @NonNull ImeOnBackInvokedDispatcher imeDispatcher, + @NonNull UserDataRepository.UserData userData) { // Compute the final shown display ID with validated cs.selfReportedDisplayId for this // session & other conditions. @@ -2211,7 +2155,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final boolean connectionWasActive = mCurInputConnection != null; // Bump up the sequence for this client and attach it. - advanceSequenceNumberLocked(); + userData.mBindingController.advanceSequenceNumber(); + mCurClient = cs; mCurInputConnection = inputConnection; mCurRemoteAccessibilityInputConnection = remoteAccessibilityInputConnection; @@ -2233,7 +2178,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (connectionIsActive != connectionWasActive) { mInputManagerInternal.notifyInputMethodConnectionActive(connectionIsActive); } - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); final var bindingController = userData.mBindingController; // If configured, we want to avoid starting up the IME if it is not supposed to be showing @@ -2250,7 +2194,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // Check if the input method is changing. // We expect the caller has already verified that the client is allowed to access this // display ID. - if (isSelectedMethodBoundLocked()) { + final String curId = bindingController.getCurId(); + if (curId != null && curId.equals(bindingController.getSelectedMethodId()) + && mDisplayIdToShowIme == mCurTokenDisplayId) { if (cs.mCurSession != null) { // Fast case: if we are already connected to the input method, // then just return it. @@ -2269,7 +2215,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0); } - InputBindResult bindResult = tryReuseConnectionLocked(cs); + InputBindResult bindResult = tryReuseConnectionLocked(userData, cs); if (bindResult != null) { return bindResult; } @@ -2369,13 +2315,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @GuardedBy("ImfLock.class") - private boolean isSelectedMethodBoundLocked() { - String curId = getCurIdLocked(); - return curId != null && curId.equals(getSelectedMethodIdLocked()) - && mDisplayIdToShowIme == mCurTokenDisplayId; - } - - @GuardedBy("ImfLock.class") private void prepareClientSwitchLocked(ClientState cs) { // If the client is changing, we need to switch over to the new // one. @@ -2388,8 +2327,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") @Nullable - private InputBindResult tryReuseConnectionLocked(@NonNull ClientState cs) { - if (hasConnectionLocked()) { + private InputBindResult tryReuseConnectionLocked(@NonNull UserDataRepository.UserData userData, + @NonNull ClientState cs) { + if (userData.mBindingController.hasMainConnection()) { if (getCurMethodLocked() != null) { // Return to client, and we will get back with it when // we have had a session made for it. @@ -2397,9 +2337,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. requestClientSessionForAccessibilityLocked(cs); return new InputBindResult( InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION, - null, null, null, getCurIdLocked(), getSequenceNumberLocked(), false); + null, null, null, getCurIdLocked(), + userData.mBindingController.getSequenceNumber(), false); } else { - long bindingDuration = SystemClock.uptimeMillis() - getLastBindTimeLocked(); + final long lastBindTime = userData.mBindingController.getLastBindTime(); + long bindingDuration = SystemClock.uptimeMillis() - lastBindTime; if (bindingDuration < TIME_TO_RECONNECT) { // In this case we have connected to the service, but // don't yet have its interface. If it hasn't been too @@ -2410,7 +2352,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // to see if we can get back in touch with the service. return new InputBindResult( InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING, - null, null, null, getCurIdLocked(), getSequenceNumberLocked(), false); + null, null, null, getCurIdLocked(), + userData.mBindingController.getSequenceNumber(), false); } else { EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, getSelectedMethodIdLocked(), bindingDuration, 0); @@ -2532,7 +2475,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT; updateSystemUiLocked(mImeWindowVis, mBackDisposition); mCurTokenDisplayId = INVALID_DISPLAY; - mCurHostInputToken = null; + mAutofillController.onResetSystemUi(); } @GuardedBy("ImfLock.class") @@ -3622,12 +3565,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. "InputMethodManagerService#startInputOrWindowGainedFocus", mDumper); final InputBindResult result; synchronized (ImfLock.class) { + final var userData = mUserDataRepository.getOrCreate(userId); // If the system is not yet ready, we shouldn't be running third party code. if (!mSystemReady) { return new InputBindResult( InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY, null /* method */, null /* accessibilitySessions */, null /* channel */, - getSelectedMethodIdLocked(), getSequenceNumberLocked(), + getSelectedMethodIdLocked(), + userData.mBindingController.getSequenceNumber(), false /* isInputMethodSuppressingSpellChecker */); } final ClientState cs = mClientController.getClient(client.asBinder()); @@ -3719,7 +3664,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. result = startInputOrWindowGainedFocusInternalLocked(startInputReason, client, windowToken, startInputFlags, softInputMode, windowFlags, editorInfo, inputConnection, remoteAccessibilityInputConnection, - unverifiedTargetSdkVersion, userId, imeDispatcher, cs); + unverifiedTargetSdkVersion, userData, imeDispatcher, cs); } finally { Binder.restoreCallingIdentity(ident); } @@ -3747,7 +3692,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo editorInfo, IRemoteInputConnection inputContext, @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, - int unverifiedTargetSdkVersion, @UserIdInt int userId, + int unverifiedTargetSdkVersion, @NonNull UserDataRepository.UserData userData, @NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull ClientState cs) { if (DEBUG) { Slog.v(TAG, "startInputOrWindowGainedFocusInternalLocked: reason=" @@ -3760,7 +3705,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. + " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode) + " windowFlags=#" + Integer.toHexString(windowFlags) + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion - + " userId=" + userId + + " userData=" + userData + " imeDispatcher=" + imeDispatcher + " cs=" + cs); } @@ -3789,7 +3734,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (editorInfo != null) { return startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, editorInfo, startInputFlags, - startInputReason, unverifiedTargetSdkVersion, imeDispatcher); + startInputReason, unverifiedTargetSdkVersion, imeDispatcher, userData); } return new InputBindResult( InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY, @@ -3821,7 +3766,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion, - imeDispatcher); + imeDispatcher, userData); didStart = true; } break; @@ -3836,7 +3781,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // Note that we can trust client's display ID as long as it matches // to the display ID obtained from the window. if (cs.mSelfReportedDisplayId != mCurTokenDisplayId) { - final var userData = mUserDataRepository.getOrCreate(userId); userData.mBindingController.unbindCurrentMethod(); } } @@ -3846,7 +3790,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion, - imeDispatcher); + imeDispatcher, userData); } else { res = InputBindResult.NULL_EDITOR_INFO; } @@ -4481,9 +4425,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private void dumpDebug(ProtoOutputStream proto, long fieldId) { synchronized (ImfLock.class) { + final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); final long token = proto.start(fieldId); proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked()); - proto.write(CUR_SEQ, getSequenceNumberLocked()); + proto.write(CUR_SEQ, userData.mBindingController.getSequenceNumber()); proto.write(CUR_CLIENT, Objects.toString(mCurClient)); mImeBindingState.dumpDebug(proto, mWindowManagerInternal); proto.write(LAST_IME_TARGET_WINDOW_NAME, @@ -4499,7 +4444,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked())); proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId); proto.write(SYSTEM_READY, mSystemReady); - proto.write(HAVE_CONNECTION, hasConnectionLocked()); + proto.write(HAVE_CONNECTION, userData.mBindingController.hasMainConnection()); proto.write(BOUND_TO_METHOD, mBoundToMethod); proto.write(IS_INTERACTIVE, mIsInteractive); proto.write(BACK_DISPOSITION, mBackDisposition); @@ -5625,14 +5570,17 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @Override public boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken, - int displayId) { + int displayId, @UserIdInt int userId) { //TODO(b/150843766): Check if Input Token is valid. final IBinder curHostInputToken; synchronized (ImfLock.class) { - if (displayId != mCurTokenDisplayId || mCurHostInputToken == null) { + if (displayId != mCurTokenDisplayId) { + return false; + } + curHostInputToken = mAutofillController.getCurHostInputToken(); + if (curHostInputToken == null) { return false; } - curHostInputToken = mCurHostInputToken; } return mInputManagerInternal.transferTouchGesture(sourceInputToken, curHostInputToken); } @@ -5677,6 +5625,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. public void onSessionForAccessibilityCreated(int accessibilityConnectionId, IAccessibilityInputMethodSession session, @UserIdInt int userId) { synchronized (ImfLock.class) { + final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); // TODO(b/305829876): Implement user ID verification if (mCurClient != null) { clearClientSessionForAccessibilityLocked(mCurClient, accessibilityConnectionId); @@ -5698,8 +5647,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mCurClient.mAccessibilitySessions); final InputBindResult res = new InputBindResult( InputBindResult.ResultCode.SUCCESS_WITH_ACCESSIBILITY_SESSION, - imeSession, accessibilityInputMethodSessions, null, getCurIdLocked(), - getSequenceNumberLocked(), false); + imeSession, accessibilityInputMethodSessions, /* channel= */ null, + getCurIdLocked(), + userData.mBindingController.getSequenceNumber(), + /* isInputMethodSuppressingSpellChecker= */ false); mCurClient.mClient.onBindAccessibilityService(res, accessibilityConnectionId); } } @@ -5709,6 +5660,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId, @UserIdInt int userId) { synchronized (ImfLock.class) { + final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); // TODO(b/305829876): Implement user ID verification if (mCurClient != null) { if (DEBUG) { @@ -5718,7 +5670,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // A11yManagerService unbinds the disabled accessibility service. We don't need // to do it here. mCurClient.mClient.onUnbindAccessibilityService( - getSequenceNumberLocked(), + userData.mBindingController.getSequenceNumber(), accessibilityConnectionId); } // We only have sessions when we bound to an input method. Remove this session @@ -5928,13 +5880,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @SuppressWarnings("GuardedBy") Consumer<ClientState> clientControllerDump = c -> { p.println(" " + c + ":"); p.println(" client=" + c.mClient); - p.println(" fallbackInputConnection=" + c.mFallbackInputConnection); p.println(" sessionRequested=" + c.mSessionRequested); - p.println( - " sessionRequestedForAccessibility=" + p.println(" sessionRequestedForAccessibility=" + c.mSessionRequestedForAccessibility); p.println(" curSession=" + c.mCurSession); p.println(" selfReportedDisplayId=" + c.mSelfReportedDisplayId); @@ -5942,19 +5892,34 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. p.println(" pid=" + c.mPid); }; mClientController.forAllClients(clientControllerDump); + final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); p.println(" mCurrentUserId=" + mCurrentUserId); p.println(" mCurMethodId=" + getSelectedMethodIdLocked()); client = mCurClient; - p.println(" mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked()); + p.println(" mCurClient=" + client + " mCurSeq=" + + userData.mBindingController.getSequenceNumber()); p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible); - mImeBindingState.dump(" ", p); - final var userData = mUserDataRepository.getOrCreate(mCurrentUserId); - p.println(" mCurId=" + getCurIdLocked() + " mHaveConnection=" + hasConnectionLocked() + mImeBindingState.dump(/* prefix= */ " ", p); + + p.println(" mCurId=" + getCurIdLocked() + + " mHaveConnection=" + userData.mBindingController.hasMainConnection() + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound=" + userData.mBindingController.isVisibleBound()); + + p.println(" mUserDataRepository="); + // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed. + @SuppressWarnings("GuardedBy") Consumer<UserDataRepository.UserData> userDataDump = + u -> { + p.println(" mUserId=" + u.mUserId); + p.println(" hasMainConnection=" + + u.mBindingController.hasMainConnection()); + p.println(" isVisibleBound=" + u.mBindingController.isVisibleBound()); + }; + mUserDataRepository.forAllUserData(userDataDump); + p.println(" mCurToken=" + getCurTokenLocked()); p.println(" mCurTokenDisplayId=" + mCurTokenDisplayId); - p.println(" mCurHostInputToken=" + mCurHostInputToken); + p.println(" mCurHostInputToken=" + mAutofillController.getCurHostInputToken()); p.println(" mCurIntent=" + getCurIntentLocked()); method = getCurMethodLocked(); p.println(" mCurMethod=" + getCurMethodLocked()); diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java index 825cfcbdf505..2b19d3e0a2ea 100644 --- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java +++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java @@ -96,5 +96,10 @@ final class UserDataRepository { mUserId = userId; mBindingController = bindingController; } + + @Override + public String toString() { + return "UserData{" + "mUserId=" + mUserId + '}'; + } } } diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java index 0049213cbf55..d932bd4e6d20 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java +++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java @@ -32,6 +32,7 @@ import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.os.Bundle; import android.os.Environment; import android.os.HandlerThread; import android.os.LocaleList; @@ -101,6 +102,11 @@ class LocaleManagerBackupHelper { // the application setting the app-locale itself. private final SharedPreferences mDelegateAppLocalePackages; private final BroadcastReceiver mUserMonitor; + // To determine whether an app is pre-archived, check for Intent.EXTRA_ARCHIVAL upon receiving + // the initial PACKAGE_ADDED broadcast. If it is indeed pre-archived, perform the data + // restoration during the second PACKAGE_ADDED broadcast, which is sent subsequently when the + // app is installed. + private final Set<String> mPkgsToRestore; LocaleManagerBackupHelper(LocaleManagerService localeManagerService, PackageManager packageManager, HandlerThread broadcastHandlerThread) { @@ -119,6 +125,7 @@ class LocaleManagerBackupHelper { mStagedData = stagedData; mDelegateAppLocalePackages = delegateAppLocalePackages != null ? delegateAppLocalePackages : createPersistedInfo(); + mPkgsToRestore = new ArraySet<>(); mUserMonitor = new UserMonitor(); IntentFilter filter = new IntentFilter(); @@ -251,6 +258,9 @@ class LocaleManagerBackupHelper { LocalesInfo localesInfo = pkgStates.get(pkgName); // Check if the application is already installed for the concerned user. if (isPackageInstalledForUser(pkgName, userId)) { + if (mPkgsToRestore != null) { + mPkgsToRestore.remove(pkgName); + } // Don't apply the restore if the locales have already been set for the app. checkExistingLocalesAndApplyRestore(pkgName, localesInfo, userId); } else { @@ -279,23 +289,18 @@ class LocaleManagerBackupHelper { /** * <p><b>Note:</b> This is invoked by service's common monitor - * {@link LocaleManagerServicePackageMonitor#onPackageAdded} when a new package is + * {@link LocaleManagerServicePackageMonitor#onPackageAddedWithExtras} when a new package is * added on device. */ - void onPackageAdded(String packageName, int uid) { - try { - synchronized (mStagedDataLock) { - cleanStagedDataForOldEntriesLocked(); - - int userId = UserHandle.getUserId(uid); - if (mStagedData.contains(userId)) { - // Perform lazy restore only if the staged data exists. - doLazyRestoreLocked(packageName, userId); - } + void onPackageAddedWithExtras(String packageName, int uid, Bundle extras) { + boolean archived = false; + if (extras != null) { + archived = extras.getBoolean(Intent.EXTRA_ARCHIVAL, false); + if (archived && mPkgsToRestore != null) { + mPkgsToRestore.add(packageName); } - } catch (Exception e) { - Slog.e(TAG, "Exception in onPackageAdded.", e); } + checkStageDataAndApplyRestore(packageName, uid); } /** @@ -305,6 +310,10 @@ class LocaleManagerBackupHelper { */ void onPackageUpdateFinished(String packageName, int uid) { int userId = UserHandle.getUserId(uid); + if (mPkgsToRestore != null && mPkgsToRestore.contains(packageName)) { + mPkgsToRestore.remove(packageName); + checkStageDataAndApplyRestore(packageName, uid); + } cleanApplicationLocalesIfNeeded(packageName, userId); } @@ -338,6 +347,25 @@ class LocaleManagerBackupHelper { } } + private void checkStageDataAndApplyRestore(String packageName, int uid) { + try { + synchronized (mStagedDataLock) { + cleanStagedDataForOldEntriesLocked(); + + int userId = UserHandle.getUserId(uid); + if (mStagedData.contains(userId)) { + if (mPkgsToRestore != null) { + mPkgsToRestore.remove(packageName); + } + // Perform lazy restore only if the staged data exists. + doLazyRestoreLocked(packageName, userId); + } + } + } catch (Exception e) { + Slog.e(TAG, "Exception in onPackageAdded.", e); + } + } + private boolean isPackageInstalledForUser(String packageName, int userId) { PackageInfo pkgInfo = null; try { diff --git a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java index ecd3614b4ff4..e0a050fbf157 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java +++ b/services/core/java/com/android/server/locales/LocaleManagerServicePackageMonitor.java @@ -17,6 +17,7 @@ package com.android.server.locales; import android.annotation.NonNull; +import android.os.Bundle; import android.os.UserHandle; import com.android.internal.content.PackageMonitor; @@ -48,8 +49,8 @@ final class LocaleManagerServicePackageMonitor extends PackageMonitor { } @Override - public void onPackageAdded(String packageName, int uid) { - mBackupHelper.onPackageAdded(packageName, uid); + public void onPackageAddedWithExtras(String packageName, int uid, Bundle extras) { + mBackupHelper.onPackageAddedWithExtras(packageName, uid, extras); } @Override diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index d6d134d892f5..17f8abe14ea0 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -333,7 +333,12 @@ public class ContextHubService extends IContextHubService.Stub { return false; } - if (didEventHappen(MESSAGE_DUPLICATION_PROBABILITY_PERCENT)) { + if (Flags.reliableMessageDuplicateDetectionService() + && didEventHappen(MESSAGE_DUPLICATION_PROBABILITY_PERCENT)) { + Log.i(TAG, "[TEST MODE] Duplicating message (" + + NUM_MESSAGES_TO_DUPLICATE + + " sends) with message sequence number: " + + message.getMessageSequenceNumber()); for (int i = 0; i < NUM_MESSAGES_TO_DUPLICATE; ++i) { handleClientMessageCallback(contextHubId, hostEndpointId, message, nanoappPermissions, messagePermissions); diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java index 77a60289d7a9..bf1b3c3f0b35 100644 --- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java @@ -168,6 +168,9 @@ public class KeySyncTask implements Runnable { } private void syncKeys() throws RemoteException { + if (mCredentialUpdated && mRecoverableKeyStoreDb.getBadRemoteGuessCounter(mUserId) != 0) { + mRecoverableKeyStoreDb.setBadRemoteGuessCounter(mUserId, 0); + } int generation = mPlatformKeyManager.getGenerationId(mUserId); if (mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) { // Application keys for the user will not be available for sync. diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 42ec1c3ad4ed..4f87c83bb0d7 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -593,6 +593,8 @@ public class NotificationManagerService extends SystemService { static final long NOTIFICATION_TTL = Duration.ofDays(3).toMillis(); + static final long NOTIFICATION_MAX_AGE_AT_POST = Duration.ofDays(14).toMillis(); + private IActivityManager mAm; private ActivityTaskManagerInternal mAtm; private ActivityManager mActivityManager; @@ -2637,27 +2639,48 @@ public class NotificationManagerService extends SystemService { * Cleanup broadcast receivers change listeners. */ public void onDestroy() { - getContext().unregisterReceiver(mIntentReceiver); - getContext().unregisterReceiver(mPackageIntentReceiver); + if (mIntentReceiver != null) { + getContext().unregisterReceiver(mIntentReceiver); + } + if (mPackageIntentReceiver != null) { + getContext().unregisterReceiver(mPackageIntentReceiver); + } if (Flags.allNotifsNeedTtl()) { - mTtlHelper.destroy(); + if (mTtlHelper != null) { + mTtlHelper.destroy(); + } } else { - getContext().unregisterReceiver(mNotificationTimeoutReceiver); + if (mNotificationTimeoutReceiver != null) { + getContext().unregisterReceiver(mNotificationTimeoutReceiver); + } + } + if (mRestoreReceiver != null) { + getContext().unregisterReceiver(mRestoreReceiver); + } + if (mLocaleChangeReceiver != null) { + getContext().unregisterReceiver(mLocaleChangeReceiver); + } + if (mSettingsObserver != null) { + mSettingsObserver.destroy(); + } + if (mRoleObserver != null) { + mRoleObserver.destroy(); } - getContext().unregisterReceiver(mRestoreReceiver); - getContext().unregisterReceiver(mLocaleChangeReceiver); - - mSettingsObserver.destroy(); - mRoleObserver.destroy(); if (mShortcutHelper != null) { mShortcutHelper.destroy(); } - mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_PREFERENCES); - mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES); - mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES); - mStatsManager.clearPullAtomCallback(DND_MODE_RULE); - mAppOps.stopWatchingMode(mAppOpsListener); - mAlarmManager.cancelAll(); + if (mStatsManager != null) { + mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_PREFERENCES); + mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES); + mStatsManager.clearPullAtomCallback(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES); + mStatsManager.clearPullAtomCallback(DND_MODE_RULE); + } + if (mAppOps != null) { + mAppOps.stopWatchingMode(mAppOpsListener); + } + if (mAlarmManager != null) { + mAlarmManager.cancelAll(); + } } protected String[] getStringArrayResource(int key) { @@ -7722,6 +7745,9 @@ public class NotificationManagerService extends SystemService { return true; } // Check if an app has been given system exemption + if (ai.uid == Process.SYSTEM_UID) { + return false; + } return mAppOps.checkOpNoThrow( AppOpsManager.OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS, ai.uid, ai.packageName) == MODE_ALLOWED; @@ -8016,6 +8042,13 @@ public class NotificationManagerService extends SystemService { return false; } + if (Flags.rejectOldNotifications() && n.hasAppProvidedWhen() && n.getWhen() > 0 + && (System.currentTimeMillis() - n.getWhen()) > NOTIFICATION_MAX_AGE_AT_POST) { + Slog.d(TAG, "Ignored enqueue for old " + n.getWhen() + " notification " + r.getKey()); + mUsageStats.registerTooOldBlocked(r); + return false; + } + return true; } @@ -11271,6 +11304,9 @@ public class NotificationManagerService extends SystemService { // Lifetime extended notifications don't need to alert on state change. record.setPostSilently(true); + // We also set FLAG_ONLY_ALERT_ONCE to avoid the notification from HUN-ing again. + record.getNotification().flags |= FLAG_ONLY_ALERT_ONCE; + mHandler.post(new EnqueueNotificationRunnable(record.getUser().getIdentifier(), record, isAppForeground, mPostNotificationTrackerFactory.newTracker(null))); diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java index e960f4ba11fd..c09077e349fd 100644 --- a/services/core/java/com/android/server/notification/NotificationUsageStats.java +++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java @@ -257,6 +257,14 @@ public class NotificationUsageStats { } } + public synchronized void registerTooOldBlocked(NotificationRecord notification) { + AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification); + for (AggregatedStats stats : aggregatedStatsArray) { + stats.numTooOld++; + } + releaseAggregatedStatsLocked(aggregatedStatsArray); + } + @GuardedBy("this") private AggregatedStats[] getAggregatedStatsLocked(NotificationRecord record) { return getAggregatedStatsLocked(record.getSbn().getPackageName()); @@ -405,6 +413,7 @@ public class NotificationUsageStats { public int numUndecoratedRemoteViews; public long mLastAccessTime; public int numImagesRemoved; + public int numTooOld; public AggregatedStats(Context context, String key) { this.key = key; @@ -535,6 +544,7 @@ public class NotificationUsageStats { maybeCount("note_over_alert_rate", (numAlertViolations - previous.numAlertViolations)); maybeCount("note_over_quota", (numQuotaViolations - previous.numQuotaViolations)); maybeCount("note_images_removed", (numImagesRemoved - previous.numImagesRemoved)); + maybeCount("not_too_old", (numTooOld - previous.numTooOld)); noisyImportance.maybeCount(previous.noisyImportance); quietImportance.maybeCount(previous.quietImportance); finalImportance.maybeCount(previous.finalImportance); @@ -570,6 +580,7 @@ public class NotificationUsageStats { previous.numAlertViolations = numAlertViolations; previous.numQuotaViolations = numQuotaViolations; previous.numImagesRemoved = numImagesRemoved; + previous.numTooOld = numTooOld; noisyImportance.update(previous.noisyImportance); quietImportance.update(previous.quietImportance); finalImportance.update(previous.finalImportance); @@ -679,6 +690,8 @@ public class NotificationUsageStats { output.append("numQuotaViolations=").append(numQuotaViolations).append("\n"); output.append(indentPlusTwo); output.append("numImagesRemoved=").append(numImagesRemoved).append("\n"); + output.append(indentPlusTwo); + output.append("numTooOld=").append(numTooOld).append("\n"); output.append(indentPlusTwo).append(noisyImportance.toString()).append("\n"); output.append(indentPlusTwo).append(quietImportance.toString()).append("\n"); output.append(indentPlusTwo).append(finalImportance.toString()).append("\n"); @@ -725,6 +738,7 @@ public class NotificationUsageStats { maybePut(dump, "notificationEnqueueRate", getEnqueueRate()); maybePut(dump, "numAlertViolations", numAlertViolations); maybePut(dump, "numImagesRemoved", numImagesRemoved); + maybePut(dump, "numTooOld", numTooOld); noisyImportance.maybePut(dump, previous.noisyImportance); quietImportance.maybePut(dump, previous.quietImportance); finalImportance.maybePut(dump, previous.finalImportance); diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index 9dcca494ca24..bf6b6521c19a 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -135,3 +135,10 @@ flag { description: "This flag controls which signal is used to handle a user switch system event" bug: "337077643" } + +flag { + name: "reject_old_notifications" + namespace: "systemui" + description: "This flag does not allow notifications older than 2 weeks old to be posted" + bug: "339833083" +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java index 99401a17f83c..235e3cd7c9d2 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java +++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java @@ -16,6 +16,10 @@ package com.android.server.ondeviceintelligence; +import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_LOADED_BUNDLE_KEY; +import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_UNLOADED_BUNDLE_KEY; +import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY; + import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeInferenceParams; import static com.android.server.ondeviceintelligence.BundleUtil.validatePfdReadOnly; import static com.android.server.ondeviceintelligence.BundleUtil.sanitizeStateParams; @@ -41,6 +45,7 @@ import android.app.ondeviceintelligence.ITokenInfoCallback; import android.app.ondeviceintelligence.OnDeviceIntelligenceException; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.content.res.Resources; @@ -105,12 +110,20 @@ public class OnDeviceIntelligenceManagerService extends SystemService { /** Handler message to {@link #resetTemporaryServices()} */ private static final int MSG_RESET_TEMPORARY_SERVICE = 0; + /** Handler message to clean up temporary broadcast keys. */ + private static final int MSG_RESET_BROADCAST_KEYS = 1; + /** Default value in absence of {@link DeviceConfig} override. */ private static final boolean DEFAULT_SERVICE_ENABLED = true; private static final String NAMESPACE_ON_DEVICE_INTELLIGENCE = "ondeviceintelligence"; + private static final String SYSTEM_PACKAGE = "android"; + + private final Executor resourceClosingExecutor = Executors.newCachedThreadPool(); private final Executor callbackExecutor = Executors.newCachedThreadPool(); + private final Executor broadcastExecutor = Executors.newCachedThreadPool(); + private final Context mContext; protected final Object mLock = new Object(); @@ -123,10 +136,14 @@ public class OnDeviceIntelligenceManagerService extends SystemService { @GuardedBy("mLock") private String[] mTemporaryServiceNames; + @GuardedBy("mLock") + private String[] mTemporaryBroadcastKeys; + @GuardedBy("mLock") + private String mBroadcastPackageName; + /** * Handler used to reset the temporary service names. */ - @GuardedBy("mLock") private Handler mTemporaryHandler; public OnDeviceIntelligenceManagerService(Context context) { @@ -482,6 +499,8 @@ public class OnDeviceIntelligenceManagerService extends SystemService { ensureRemoteIntelligenceServiceInitialized(); mRemoteOnDeviceIntelligenceService.run( IOnDeviceIntelligenceService::notifyInferenceServiceConnected); + broadcastExecutor.execute( + () -> registerModelLoadingBroadcasts(service)); service.registerRemoteStorageService( getIRemoteStorageService()); } catch (RemoteException ex) { @@ -493,6 +512,56 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } } + private void registerModelLoadingBroadcasts(IOnDeviceSandboxedInferenceService service) { + String[] modelBroadcastKeys; + try { + modelBroadcastKeys = getBroadcastKeys(); + } catch (Resources.NotFoundException e) { + Slog.d(TAG, "Skipping model broadcasts as broadcast intents configured."); + return; + } + + Bundle bundle = new Bundle(); + bundle.putBoolean(REGISTER_MODEL_UPDATE_CALLBACK_BUNDLE_KEY, true); + try { + service.updateProcessingState(bundle, new IProcessingUpdateStatusCallback.Stub() { + @Override + public void onSuccess(PersistableBundle statusParams) { + Binder.clearCallingIdentity(); + synchronized (mLock) { + if (statusParams.containsKey(MODEL_LOADED_BUNDLE_KEY)) { + String modelLoadedBroadcastKey = modelBroadcastKeys[0]; + if (modelLoadedBroadcastKey != null + && !modelLoadedBroadcastKey.isEmpty()) { + final Intent intent = new Intent(modelLoadedBroadcastKey); + intent.setPackage(mBroadcastPackageName); + mContext.sendBroadcast(intent, + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE); + } + } else if (statusParams.containsKey(MODEL_UNLOADED_BUNDLE_KEY)) { + String modelUnloadedBroadcastKey = modelBroadcastKeys[1]; + if (modelUnloadedBroadcastKey != null + && !modelUnloadedBroadcastKey.isEmpty()) { + final Intent intent = new Intent(modelUnloadedBroadcastKey); + intent.setPackage(mBroadcastPackageName); + mContext.sendBroadcast(intent, + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE); + } + } + } + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + Slog.e(TAG, "Failed to register model loading callback with status code", + new OnDeviceIntelligenceException(errorCode, errorMessage)); + } + }); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to register model loading callback with status code", e); + } + } + @NonNull private IRemoteStorageService.Stub getIRemoteStorageService() { return new IRemoteStorageService.Stub() { @@ -629,6 +698,20 @@ public class OnDeviceIntelligenceManagerService extends SystemService { R.string.config_defaultOnDeviceSandboxedInferenceService)}; } + protected String[] getBroadcastKeys() throws Resources.NotFoundException { + // TODO 329240495 : Consider a small class with explicit field names for the two services + synchronized (mLock) { + if (mTemporaryBroadcastKeys != null && mTemporaryBroadcastKeys.length == 2) { + return mTemporaryBroadcastKeys; + } + } + + return new String[]{mContext.getResources().getString( + R.string.config_onDeviceIntelligenceModelLoadedBroadcastKey), + mContext.getResources().getString( + R.string.config_onDeviceIntelligenceModelUnloadedBroadcastKey)}; + } + @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void setTemporaryServices(@NonNull String[] componentNames, int durationMs) { Objects.requireNonNull(componentNames); @@ -645,25 +728,26 @@ public class OnDeviceIntelligenceManagerService extends SystemService { mRemoteOnDeviceIntelligenceService.unbind(); mRemoteOnDeviceIntelligenceService = null; } - if (mTemporaryHandler == null) { - mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) { - @Override - public void handleMessage(Message msg) { - if (msg.what == MSG_RESET_TEMPORARY_SERVICE) { - synchronized (mLock) { - resetTemporaryServices(); - } - } else { - Slog.wtf(TAG, "invalid handler msg: " + msg); - } - } - }; - } else { - mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE); + + if (durationMs != -1) { + getTemporaryHandler().sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, + durationMs); } + } + } + @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) + public void setModelBroadcastKeys(@NonNull String[] broadcastKeys, String receiverPackageName, + int durationMs) { + Objects.requireNonNull(broadcastKeys); + enforceShellOnly(Binder.getCallingUid(), "setModelBroadcastKeys"); + mContext.enforceCallingPermission( + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); + synchronized (mLock) { + mTemporaryBroadcastKeys = broadcastKeys; + mBroadcastPackageName = receiverPackageName; if (durationMs != -1) { - mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs); + getTemporaryHandler().sendEmptyMessageDelayed(MSG_RESET_BROADCAST_KEYS, durationMs); } } } @@ -751,4 +835,28 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } } } + + private synchronized Handler getTemporaryHandler() { + if (mTemporaryHandler == null) { + mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) { + @Override + public void handleMessage(Message msg) { + if (msg.what == MSG_RESET_TEMPORARY_SERVICE) { + synchronized (mLock) { + resetTemporaryServices(); + } + } else if (msg.what == MSG_RESET_BROADCAST_KEYS) { + synchronized (mLock) { + mTemporaryBroadcastKeys = null; + mBroadcastPackageName = SYSTEM_PACKAGE; + } + } else { + Slog.wtf(TAG, "invalid handler msg: " + msg); + } + } + }; + } + + return mTemporaryHandler; + } } diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java index a76d8a31405d..5744b5c3c2c4 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java +++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java @@ -43,6 +43,8 @@ final class OnDeviceIntelligenceShellCommand extends ShellCommand { return setTemporaryServices(); case "get-services": return getConfiguredServices(); + case "set-model-broadcasts": + return setBroadcastKeys(); default: return handleDefaultCommands(cmd); } @@ -62,12 +64,18 @@ final class OnDeviceIntelligenceShellCommand extends ShellCommand { pw.println(" To reset, call without any arguments."); pw.println(" get-services To get the names of services that are currently being used."); + pw.println( + " set-model-broadcasts [ModelLoadedBroadcastKey] [ModelUnloadedBroadcastKey] " + + "[ReceiverPackageName] " + + "[DURATION] To set the names of broadcast intent keys that are to be " + + "emitted for cts tests."); } private int setTemporaryServices() { final PrintWriter out = getOutPrintWriter(); final String intelligenceServiceName = getNextArg(); final String inferenceServiceName = getNextArg(); + if (getRemainingArgsCount() == 0 && intelligenceServiceName == null && inferenceServiceName == null) { mService.resetTemporaryServices(); @@ -79,7 +87,8 @@ final class OnDeviceIntelligenceShellCommand extends ShellCommand { Objects.requireNonNull(inferenceServiceName); final int duration = Integer.parseInt(getNextArgRequired()); mService.setTemporaryServices( - new String[]{intelligenceServiceName, inferenceServiceName}, duration); + new String[]{intelligenceServiceName, inferenceServiceName}, + duration); out.println("OnDeviceIntelligenceService temporarily set to " + intelligenceServiceName + " \n and \n OnDeviceTrustedInferenceService set to " + inferenceServiceName + " for " + duration + "ms"); @@ -93,4 +102,22 @@ final class OnDeviceIntelligenceShellCommand extends ShellCommand { + " \n and \n OnDeviceTrustedInferenceService set to : " + services[1]); return 0; } + + private int setBroadcastKeys() { + final PrintWriter out = getOutPrintWriter(); + final String modelLoadedKey = getNextArgRequired(); + final String modelUnloadedKey = getNextArgRequired(); + final String receiverPackageName = getNextArg(); + + final int duration = Integer.parseInt(getNextArgRequired()); + mService.setModelBroadcastKeys( + new String[]{modelLoadedKey, modelUnloadedKey}, receiverPackageName, duration); + out.println("OnDeviceIntelligence Model Loading broadcast keys temporarily set to " + + modelLoadedKey + + " \n and \n OnDeviceTrustedInferenceService set to " + modelUnloadedKey + + "\n and Package name set to : " + receiverPackageName + + " for " + duration + "ms"); + return 0; + } + }
\ No newline at end of file diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java index 9ba88aa18ce6..fe774aa75efc 100644 --- a/services/core/java/com/android/server/pm/AppDataHelper.java +++ b/services/core/java/com/android/server/pm/AppDataHelper.java @@ -504,9 +504,12 @@ public class AppDataHelper { } else { storageFlags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE; } - List<String> deferPackages = reconcileAppsDataLI(StorageManager.UUID_PRIVATE_INTERNAL, - UserHandle.USER_SYSTEM, storageFlags, true /* migrateAppData */, - true /* onlyCoreApps */); + final List<String> deferPackages; + synchronized (mPm.mInstallLock) { + deferPackages = reconcileAppsDataLI(StorageManager.UUID_PRIVATE_INTERNAL, + UserHandle.USER_SYSTEM, storageFlags, true /* migrateAppData */, + true /* onlyCoreApps */); + } Future<?> prepareAppDataFuture = SystemServerInitThreadPool.submit(() -> { TimingsTraceLog traceLog = new TimingsTraceLog("SystemServerTimingAsync", Trace.TRACE_TAG_PACKAGE_MANAGER); diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index 3a0f7fb4b432..02bd3cca5eb4 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -376,11 +376,14 @@ final class RemovePackageHelper { @NonNull PackageRemovedInfo outInfo, int flags, boolean writeSettings) { String packageName = deletedPs.getPackageName(); if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + deletedPs); + final boolean shouldDeletePackageSetting = + shouldDeletePackageSetting(deletedPs, targetUserId, allUserHandles, flags); // Retrieve object to delete permissions for shared user later on final AndroidPackage deletedPkg = deletedPs.getPkg(); // Delete all the data and states related to this package. - clearPackageStateForUserLIF(deletedPs, targetUserId, flags); + clearPackageStateForUserLIF(deletedPs, + shouldDeletePackageSetting ? UserHandle.USER_ALL : targetUserId, flags); // Delete from mPackages removePackageLI(packageName, (flags & PackageManager.DELETE_CHATTY) != 0); @@ -392,7 +395,7 @@ final class RemovePackageHelper { deletedPs.setPkg(null); } - if (shouldDeletePackageSetting(deletedPs, targetUserId, allUserHandles, flags)) { + if (shouldDeletePackageSetting) { // Delete from mSettings final SparseBooleanArray changedUsers = new SparseBooleanArray(); synchronized (mPm.mLock) { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 1309e44af6d3..41d6288d4411 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -2139,10 +2139,17 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile continue; } + ComponentName unflattenOriginalComponentName = ComponentName.unflattenFromString( + originalComponentName); + if (unflattenOriginalComponentName == null) { + Slog.d(TAG, "Incorrect component name from the attributes"); + continue; + } + activityInfos.add( new ArchiveState.ArchiveActivityInfo( title, - ComponentName.unflattenFromString(originalComponentName), + unflattenOriginalComponentName, iconPath, monochromeIconPath)); } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index f6487ceea43b..b1976cd0d13b 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1371,7 +1371,7 @@ public class UserManagerService extends IUserManager.Stub { for (int i = 0; i < userSize; i++) { UserInfo ui = mUsers.valueAt(i).info; if ((excludePartial && ui.partial) - || (excludeDying && isDyingLU(ui)) + || (excludeDying && mRemovingUserIds.get(ui.id)) || (excludePreCreated && ui.preCreated)) { continue; } @@ -1381,17 +1381,6 @@ public class UserManagerService extends IUserManager.Stub { } } - @GuardedBy("mUsersLock") - private boolean isDyingLU(UserInfo ui) { - if (mRemovingUserIds.get(ui.id)) { - return true; - } - if (ui.isEphemeral() && ui.isInitialized() && ui.id != getCurrentUserId()) { - return true; - } - return false; - } - @Override public List<UserInfo> getProfiles(@UserIdInt int userId, boolean enabledOnly) { boolean returnFullInfo; diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index 4e02470e087d..483d308ae8ad 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -859,7 +859,6 @@ public class UserRestrictionsUtils { break; case android.provider.Settings.System.SCREEN_BRIGHTNESS: - case android.provider.Settings.System.SCREEN_BRIGHTNESS_FLOAT: case android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE: if (callingUid == Process.SYSTEM_UID) { return false; diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java index 93f26aefb692..c85ceac9ea55 100644 --- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java @@ -642,6 +642,8 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve .getPackages() .get(0) .getVersionRolledBackFrom(); + Slog.i(TAG, "Rolling back high impact rollback for package: " + + firstRollback.getPackageName()); rollbackPackage(sortedHighImpactRollbacks.get(0), firstRollback, rollbackReason); } diff --git a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java index 519c0edfc532..7fc02923bfed 100644 --- a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java +++ b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java @@ -293,6 +293,8 @@ public final class WatchdogRollbackLogger { return "REASON_APP_NOT_RESPONDING"; case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT: return "REASON_NATIVE_CRASH_DURING_BOOT"; + case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING: + return "REASON_BOOT_LOOP"; default: return "UNKNOWN"; } diff --git a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java index 2bf0b2cd4f4a..55f85ea27c82 100644 --- a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java +++ b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java @@ -22,6 +22,8 @@ import static android.security.attestationverification.AttestationVerificationMa import static android.security.attestationverification.AttestationVerificationManager.RESULT_FAILURE; import static android.security.attestationverification.AttestationVerificationManager.RESULT_UNKNOWN; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.os.Bundle; import android.os.IBinder; @@ -31,12 +33,20 @@ import android.security.attestationverification.AttestationProfile; import android.security.attestationverification.IAttestationVerificationManagerService; import android.security.attestationverification.IVerificationResult; import android.security.attestationverification.VerificationToken; +import android.text.TextUtils; import android.util.ExceptionUtils; +import android.util.IndentingPrintWriter; import android.util.Slog; +import android.util.TimeUtils; import com.android.internal.infra.AndroidFuture; +import com.android.internal.util.DumpUtils; import com.android.server.SystemService; +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayDeque; + /** * A {@link SystemService} which provides functionality related to verifying attestations of * (usually) remote computing environments. @@ -46,11 +56,13 @@ import com.android.server.SystemService; public class AttestationVerificationManagerService extends SystemService { private static final String TAG = "AVF"; + private static final int DUMP_EVENT_LOG_SIZE = 10; private final AttestationVerificationPeerDeviceVerifier mPeerDeviceVerifier; + private final DumpLogger mDumpLogger = new DumpLogger(); public AttestationVerificationManagerService(final Context context) throws Exception { super(context); - mPeerDeviceVerifier = new AttestationVerificationPeerDeviceVerifier(context); + mPeerDeviceVerifier = new AttestationVerificationPeerDeviceVerifier(context, mDumpLogger); } private final IBinder mService = new IAttestationVerificationManagerService.Stub() { @@ -83,6 +95,28 @@ public class AttestationVerificationManagerService extends SystemService { private void enforceUsePermission() { getContext().enforceCallingOrSelfPermission(USE_ATTESTATION_VERIFICATION_SERVICE, null); } + + @Override + protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, + @Nullable String[] args) { + if (!android.security.Flags.dumpAttestationVerifications()) { + super.dump(fd, writer, args); + return; + } + + if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, writer)) return; + + final IndentingPrintWriter fout = new IndentingPrintWriter(writer, " "); + + fout.print("AttestationVerificationManagerService"); + fout.println(); + fout.increaseIndent(); + + fout.println("Event Log:"); + fout.increaseIndent(); + mDumpLogger.dumpTo(fout); + fout.decreaseIndent(); + } }; private void verifyAttestationForAllVerifiers( @@ -119,4 +153,45 @@ public class AttestationVerificationManagerService extends SystemService { Slog.d(TAG, "Started"); publishBinderService(Context.ATTESTATION_VERIFICATION_SERVICE, mService); } + + + static class DumpLogger { + private final ArrayDeque<DumpData> mData = new ArrayDeque<>(DUMP_EVENT_LOG_SIZE); + private int mEventsLogged = 0; + + void logAttempt(DumpData data) { + synchronized (mData) { + if (mData.size() == DUMP_EVENT_LOG_SIZE) { + mData.removeFirst(); + } + + mEventsLogged++; + data.mEventNumber = mEventsLogged; + + data.mEventTimeMs = System.currentTimeMillis(); + + mData.add(data); + } + } + + void dumpTo(IndentingPrintWriter writer) { + synchronized (mData) { + for (DumpData data : mData.reversed()) { + writer.println( + TextUtils.formatSimple("Verification #%d [%s]", data.mEventNumber, + TimeUtils.formatForLogging(data.mEventTimeMs))); + writer.increaseIndent(); + data.dumpTo(writer); + writer.decreaseIndent(); + } + } + } + } + + abstract static class DumpData { + protected int mEventNumber = -1; + protected long mEventTimeMs = -1; + + abstract void dumpTo(IndentingPrintWriter writer); + } } diff --git a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java index 72a402d7a58c..945a3400d971 100644 --- a/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java +++ b/services/core/java/com/android/server/security/AttestationVerificationPeerDeviceVerifier.java @@ -30,15 +30,19 @@ import static com.android.server.security.AndroidKeystoreAttestationVerification import static java.nio.charset.StandardCharsets.UTF_8; import android.annotation.NonNull; +import android.annotation.SuppressLint; import android.content.Context; import android.os.Build; import android.os.Bundle; +import android.security.attestationverification.AttestationVerificationManager; import android.security.attestationverification.AttestationVerificationManager.LocalBindingType; +import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Slog; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.security.AttestationVerificationManagerService.DumpLogger; import org.json.JSONObject; @@ -71,7 +75,9 @@ import java.util.Set; /** * Verifies Android key attestation according to the - * {@link android.security.attestationverification.AttestationVerificationManager#PROFILE_PEER_DEVICE PROFILE_PEER_DEVICE} + * {@link + * android.security.attestationverification.AttestationVerificationManager#PROFILE_PEER_DEVICE + * PROFILE_PEER_DEVICE} * profile. * * <p> @@ -118,9 +124,12 @@ class AttestationVerificationPeerDeviceVerifier { private final LocalDate mTestLocalPatchDate; private final CertificateFactory mCertificateFactory; private final CertPathValidator mCertPathValidator; + private final DumpLogger mDumpLogger; - AttestationVerificationPeerDeviceVerifier(@NonNull Context context) throws Exception { + AttestationVerificationPeerDeviceVerifier(@NonNull Context context, + @NonNull DumpLogger dumpLogger) throws Exception { mContext = Objects.requireNonNull(context); + mDumpLogger = dumpLogger; mCertificateFactory = CertificateFactory.getInstance("X.509"); mCertPathValidator = CertPathValidator.getInstance("PKIX"); mTrustAnchors = getTrustAnchors(); @@ -132,9 +141,10 @@ class AttestationVerificationPeerDeviceVerifier { // Use ONLY for hermetic unit testing. @VisibleForTesting AttestationVerificationPeerDeviceVerifier(@NonNull Context context, - Set<TrustAnchor> trustAnchors, boolean revocationEnabled, + DumpLogger dumpLogger, Set<TrustAnchor> trustAnchors, boolean revocationEnabled, LocalDate systemDate, LocalDate localPatchDate) throws Exception { mContext = Objects.requireNonNull(context); + mDumpLogger = dumpLogger; mCertificateFactory = CertificateFactory.getInstance("X.509"); mCertPathValidator = CertPathValidator.getInstance("PKIX"); mTrustAnchors = trustAnchors; @@ -153,63 +163,90 @@ class AttestationVerificationPeerDeviceVerifier { * bounded at the end by {@code -----END CERTIFICATE-----}. * * @param localBindingType Only {@code TYPE_PUBLIC_KEY} and {@code TYPE_CHALLENGE} supported. - * @param requirements Only {@code PARAM_PUBLIC_KEY} and {@code PARAM_CHALLENGE} supported. - * @param attestation Certificates should be DER encoded with leaf certificate appended first. + * @param requirements Only {@code PARAM_PUBLIC_KEY} and {@code PARAM_CHALLENGE} supported. + * @param attestation Certificates should be DER encoded with leaf certificate appended + * first. */ int verifyAttestation( @LocalBindingType int localBindingType, @NonNull Bundle requirements, @NonNull byte[] attestation) { + + MyDumpData dumpData = new MyDumpData(); + + int result = + verifyAttestationInternal(localBindingType, requirements, attestation, dumpData); + dumpData.mResult = result; + mDumpLogger.logAttempt(dumpData); + return result; + } + + private int verifyAttestationInternal( + @LocalBindingType int localBindingType, + @NonNull Bundle requirements, + @NonNull byte[] attestation, + @NonNull MyDumpData dumpData) { if (mCertificateFactory == null) { debugVerboseLog("Unable to access CertificateFactory"); return RESULT_FAILURE; } + dumpData.mCertificationFactoryAvailable = true; if (mCertPathValidator == null) { debugVerboseLog("Unable to access CertPathValidator"); return RESULT_FAILURE; } + dumpData.mCertPathValidatorAvailable = true; + // Check if the provided local binding type is supported and if the provided requirements // "match" the binding type. if (!validateAttestationParameters(localBindingType, requirements)) { return RESULT_FAILURE; } + dumpData.mAttestationParametersOk = true; + + // To provide the most information in the dump logs, we track the failure state but keep + // verifying the rest of the attestation. For code safety, there are no transitions past + // here to set failed = false + boolean failed = false; try { // First: parse and validate the certificate chain. final List<X509Certificate> certificateChain = getCertificates(attestation); // (returns void, but throws CertificateException and other similar Exceptions) validateCertificateChain(certificateChain); + dumpData.mCertChainOk = true; final var leafCertificate = certificateChain.get(0); final var attestationExtension = fromCertificate(leafCertificate); // Second: verify if the attestation satisfies the "peer device" profile. - if (!checkAttestationForPeerDeviceProfile(attestationExtension)) { - return RESULT_FAILURE; + if (!checkAttestationForPeerDeviceProfile(attestationExtension, dumpData)) { + failed = true; } // Third: check if the attestation satisfies local binding requirements. if (!checkLocalBindingRequirements( - leafCertificate, attestationExtension, localBindingType, requirements)) { - return RESULT_FAILURE; + leafCertificate, attestationExtension, localBindingType, requirements, + dumpData)) { + failed = true; } - - return RESULT_SUCCESS; } catch (CertificateException | CertPathValidatorException - | InvalidAlgorithmParameterException | IOException e) { + | InvalidAlgorithmParameterException | IOException e) { // Catch all non-RuntimeExpceptions (all of these are thrown by either getCertificates() // or validateCertificateChain() or // AndroidKeystoreAttestationVerificationAttributes.fromCertificate()) debugVerboseLog("Unable to parse/validate Android Attestation certificate(s)", e); - return RESULT_FAILURE; + failed = true; } catch (RuntimeException e) { // Catch everyting else (RuntimeExpcetions), since we don't want to throw any exceptions // out of this class/method. debugVerboseLog("Unexpected error", e); - return RESULT_FAILURE; + failed = true; } + + return failed ? RESULT_FAILURE : RESULT_SUCCESS; } @NonNull @@ -255,7 +292,7 @@ class AttestationVerificationPeerDeviceVerifier { private void validateCertificateChain(List<X509Certificate> certificates) throws CertificateException, CertPathValidatorException, - InvalidAlgorithmParameterException { + InvalidAlgorithmParameterException { if (certificates.size() < 2) { debugVerboseLog("Certificate chain less than 2 in size."); throw new CertificateException("Certificate chain less than 2 in size."); @@ -277,7 +314,7 @@ class AttestationVerificationPeerDeviceVerifier { private Set<TrustAnchor> getTrustAnchors() throws CertPathValidatorException { Set<TrustAnchor> modifiableSet = new HashSet<>(); try { - for (String certString: getTrustAnchorResources()) { + for (String certString : getTrustAnchorResources()) { modifiableSet.add( new TrustAnchor((X509Certificate) mCertificateFactory.generateCertificate( new ByteArrayInputStream(getCertificateBytes(certString))), null)); @@ -307,8 +344,9 @@ class AttestationVerificationPeerDeviceVerifier { @NonNull X509Certificate leafCertificate, @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes, @LocalBindingType int localBindingType, - @NonNull Bundle requirements) { + @NonNull Bundle requirements, MyDumpData dumpData) { // First: check non-optional (for the given local binding type) requirements. + dumpData.mBindingType = localBindingType; switch (localBindingType) { case TYPE_PUBLIC_KEY: // Verify leaf public key matches provided public key. @@ -336,9 +374,11 @@ class AttestationVerificationPeerDeviceVerifier { throw new IllegalArgumentException("Unsupported local binding type " + localBindingTypeToString(localBindingType)); } + dumpData.mBindingOk = true; // Second: check specified optional requirements. if (requirements.containsKey(PARAM_OWNED_BY_SYSTEM)) { + dumpData.mSystemOwnershipChecked = true; if (requirements.getBoolean(PARAM_OWNED_BY_SYSTEM)) { // Verify key is owned by the system. final boolean ownedBySystem = checkOwnedBySystem( @@ -347,6 +387,7 @@ class AttestationVerificationPeerDeviceVerifier { debugVerboseLog("Certificate public key is not owned by the AndroidSystem."); return false; } + dumpData.mSystemOwned = true; } else { throw new IllegalArgumentException("The value of the requirement key " + PARAM_OWNED_BY_SYSTEM @@ -359,73 +400,98 @@ class AttestationVerificationPeerDeviceVerifier { } private boolean checkAttestationForPeerDeviceProfile( - @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes) { + @NonNull AndroidKeystoreAttestationVerificationAttributes attestationAttributes, + MyDumpData dumpData) { + boolean result = true; + // Checks for support of Keymaster 4. if (attestationAttributes.getAttestationVersion() < 3) { debugVerboseLog("Attestation version is not at least 3 (Keymaster 4)."); - return false; + result = false; + } else { + dumpData.mAttestationVersionAtLeast3 = true; } // Checks for support of Keymaster 4. if (attestationAttributes.getKeymasterVersion() < 4) { debugVerboseLog("Keymaster version is not at least 4."); - return false; + result = false; + } else { + dumpData.mKeymasterVersionAtLeast4 = true; } // First two characters are Android OS version. if (attestationAttributes.getKeyOsVersion() < 100000) { debugVerboseLog("Android OS version is not 10+."); - return false; + result = false; + } else { + dumpData.mOsVersionAtLeast10 = true; } if (!attestationAttributes.isAttestationHardwareBacked()) { debugVerboseLog("Key is not HW backed."); - return false; + result = false; + } else { + dumpData.mKeyHwBacked = true; } if (!attestationAttributes.isKeymasterHardwareBacked()) { debugVerboseLog("Keymaster is not HW backed."); - return false; + result = false; + } else { + dumpData.mKeymasterHwBacked = true; } if (attestationAttributes.getVerifiedBootState() != VERIFIED) { debugVerboseLog("Boot state not Verified."); - return false; + result = false; + } else { + dumpData.mBootStateIsVerified = true; } try { if (!attestationAttributes.isVerifiedBootLocked()) { debugVerboseLog("Verified boot state is not locked."); - return false; + result = false; + } else { + dumpData.mVerifiedBootStateLocked = true; } } catch (IllegalStateException e) { debugVerboseLog("VerifiedBootLocked is not set.", e); - return false; + result = false; } // Patch level integer YYYYMM is expected to be within 1 year of today. if (!isValidPatchLevel(attestationAttributes.getKeyOsPatchLevel())) { debugVerboseLog("OS patch level is not within valid range."); - return false; + result = false; + } else { + dumpData.mOsPatchLevelInRange = true; } // Patch level integer YYYYMMDD is expected to be within 1 year of today. if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel())) { debugVerboseLog("Boot patch level is not within valid range."); - return false; + result = false; + } else { + dumpData.mKeyBootPatchLevelInRange = true; } if (!isValidPatchLevel(attestationAttributes.getKeyVendorPatchLevel())) { debugVerboseLog("Vendor patch level is not within valid range."); - return false; + result = false; + } else { + dumpData.mKeyVendorPatchLevelInRange = true; } if (!isValidPatchLevel(attestationAttributes.getKeyBootPatchLevel())) { debugVerboseLog("Boot patch level is not within valid range."); - return false; + result = false; + } else { + dumpData.mKeyBootPatchLevelInRange = true; } - return true; + return result; } private boolean checkPublicKey( @@ -609,4 +675,99 @@ class AttestationVerificationPeerDeviceVerifier { Slog.v(TAG, str); } } + + /* Mutable data class for tracking dump data from verifications. */ + private static class MyDumpData extends AttestationVerificationManagerService.DumpData { + + // Top-Level Result + int mResult = -1; + + // Configuration/Setup preconditions + boolean mCertificationFactoryAvailable = false; + boolean mCertPathValidatorAvailable = false; + + // AttestationParameters (Valid Input Only) + boolean mAttestationParametersOk = false; + + // Certificate Chain (Structure & Chaining Conditions) + boolean mCertChainOk = false; + + // Binding + boolean mBindingOk = false; + int mBindingType = -1; + + // System Ownership + boolean mSystemOwnershipChecked = false; + boolean mSystemOwned = false; + + // Android Keystore attestation properties + boolean mOsVersionAtLeast10 = false; + boolean mKeyHwBacked = false; + boolean mAttestationVersionAtLeast3 = false; + boolean mKeymasterVersionAtLeast4 = false; + boolean mKeymasterHwBacked = false; + boolean mBootStateIsVerified = false; + boolean mVerifiedBootStateLocked = false; + boolean mOsPatchLevelInRange = false; + boolean mKeyBootPatchLevelInRange = false; + boolean mKeyVendorPatchLevelInRange = false; + + @SuppressLint("WrongConstant") + @Override + public void dumpTo(IndentingPrintWriter writer) { + writer.println( + "Result: " + AttestationVerificationManager.verificationResultCodeToString( + mResult)); + if (!mCertificationFactoryAvailable) { + writer.println("Certificate Factory Unavailable"); + return; + } + if (!mCertPathValidatorAvailable) { + writer.println("Cert Path Validator Unavailable"); + return; + } + if (!mAttestationParametersOk) { + writer.println("Attestation parameters set incorrectly."); + return; + } + + writer.println("Certificate Chain Valid (inc. Trust Anchor): " + booleanToOkFail( + mCertChainOk)); + if (!mCertChainOk) { + return; + } + + // Binding + writer.println("Local Binding: " + booleanToOkFail(mBindingOk)); + writer.increaseIndent(); + writer.println("Binding Type: " + mBindingType); + writer.decreaseIndent(); + + if (mSystemOwnershipChecked) { + writer.println("System Ownership: " + booleanToOkFail(mSystemOwned)); + } + + // Keystore Attestation params + writer.println("KeyStore Attestation Parameters"); + writer.increaseIndent(); + writer.println("OS Version >= 10: " + booleanToOkFail(mOsVersionAtLeast10)); + writer.println("OS Patch Level in Range: " + booleanToOkFail(mOsPatchLevelInRange)); + writer.println( + "Attestation Version >= 3: " + booleanToOkFail(mAttestationVersionAtLeast3)); + writer.println("Keymaster Version >= 4: " + booleanToOkFail(mKeymasterVersionAtLeast4)); + writer.println("Keymaster HW-Backed: " + booleanToOkFail(mKeymasterHwBacked)); + writer.println("Key is HW Backed: " + booleanToOkFail(mKeyHwBacked)); + writer.println("Boot State is VERIFIED: " + booleanToOkFail(mBootStateIsVerified)); + writer.println("Verified Boot is LOCKED: " + booleanToOkFail(mVerifiedBootStateLocked)); + writer.println( + "Key Boot Level in Range: " + booleanToOkFail(mKeyBootPatchLevelInRange)); + writer.println("Key Vendor Patch Level in Range: " + booleanToOkFail( + mKeyVendorPatchLevelInRange)); + writer.decreaseIndent(); + } + + private String booleanToOkFail(boolean value) { + return value ? "OK" : "FAILURE"; + } + } } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 2c67207f407c..4264e912f25a 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -20,9 +20,7 @@ import static android.Manifest.permission.CONTROL_DEVICE_STATE; import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS; -import static android.app.StatusBarManager.DISABLE2_MASK; import static android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE; -import static android.app.StatusBarManager.DISABLE_MASK; import static android.app.StatusBarManager.NAV_BAR_MODE_DEFAULT; import static android.app.StatusBarManager.NAV_BAR_MODE_KIDS; import static android.app.StatusBarManager.NavBarMode; @@ -222,9 +220,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D int what1; int what2; IBinder token; - private String mReason; - DisableRecord(int userId, IBinder token) { + public DisableRecord(int userId, IBinder token) { this.userId = userId; this.token = token; try { @@ -237,12 +234,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void binderDied() { Slog.i(TAG, "binder died for pkg=" + pkg); - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - disableForUser(info, token, pkg, userId, "Binder Died"); + disableForUser(0, token, pkg, userId); + disable2ForUser(0, token, pkg, userId); token.unlinkToDeath(this, 0); } - public void setFlags(int what, int which, String pkg, String reason) { + public void setFlags(int what, int which, String pkg) { switch (which) { case 1: what1 = what; @@ -256,7 +253,6 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D break; } this.pkg = pkg; - this.mReason = reason; } public int getFlags(int which) { @@ -275,8 +271,8 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public String toString() { - return String.format("userId=%d what1=0x%08X what2=0x%08X pkg=%s token=%s reason=%s", - userId, what1, what2, pkg, token, mReason); + return String.format("userId=%d what1=0x%08X what2=0x%08X pkg=%s token=%s", + userId, what1, what2, pkg, token); } } @@ -954,7 +950,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D if (mBar != null) { try { - mBar.togglePanel(); + mBar.toggleNotificationsPanel(); } catch (RemoteException ex) { } } @@ -1184,59 +1180,57 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D return mTracingEnabled; } - /** - * @deprecated - * Disable some features in the status bar. - * - * This method is deprecated and callers should use - * {@link #disableForUser(StatusBarManager.DisableInfo, IBinder, String, int, String)} - * - * @hide - */ - @Deprecated + // TODO(b/117478341): make it aware of multi-display if needed. @Override public void disable(int what, IBinder token, String pkg) { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(what & DISABLE_MASK, - what & DISABLE2_MASK); - disableForUser(info, token, pkg, mCurrentUserId, null); + disableForUser(what, token, pkg, mCurrentUserId); } + // TODO(b/117478341): make it aware of multi-display if needed. + @Override + public void disableForUser(int what, IBinder token, String pkg, int userId) { + enforceStatusBar(); + + synchronized (mLock) { + disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 1); + } + } + + // TODO(b/117478341): make it aware of multi-display if needed. /** - * @deprecated - * Disable some features in the status bar. - * - * This method is deprecated and callers should use - * {@link #disableForUser(StatusBarManager.DisableInfo, IBinder, String, int, String)} + * Disable additional status bar features. Pass the bitwise-or of the DISABLE2_* flags. + * To re-enable everything, pass {@link #DISABLE2_NONE}. * - * @hide + * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags. */ - @Deprecated @Override public void disable2(int what, IBinder token, String pkg) { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(what & DISABLE_MASK, - what & DISABLE2_MASK); - disableForUser(info, token, pkg, mCurrentUserId, null); + disable2ForUser(what, token, pkg, mCurrentUserId); } // TODO(b/117478341): make it aware of multi-display if needed. + /** + * Disable additional status bar features for a given user. Pass the bitwise-or of the + * DISABLE2_* flags. To re-enable everything, pass {@link #DISABLE_NONE}. + * + * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags. + */ @Override - public void disableForUser(StatusBarManager.DisableInfo disableInfo, IBinder token, String pkg, - int userId, String reason) { + public void disable2ForUser(int what, IBinder token, String pkg, int userId) { enforceStatusBar(); + synchronized (mLock) { - Pair<Integer, Integer> flags = disableInfo.toFlags(); - disableLocked(DEFAULT_DISPLAY, userId, flags.first, token, pkg, 1, reason); - disableLocked(DEFAULT_DISPLAY, userId, flags.second, token, pkg, 2, reason); + disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 2); } } private void disableLocked(int displayId, int userId, int what, IBinder token, String pkg, - int whichFlag, String reason) { + int whichFlag) { // It's important that the the callback and the call to mBar get done // in the same order when multiple threads are calling this function // so they are paired correctly. The messages on the handler will be // handled in the order they were enqueued, but will be outside the lock. - manageDisableListLocked(userId, what, token, pkg, whichFlag, reason); + manageDisableListLocked(userId, what, token, pkg, whichFlag); // Ensure state for the current user is applied, even if passed a non-current user. final int net1 = gatherDisableActionsLocked(mCurrentUserId, 1); @@ -1385,7 +1379,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D // also allows calls from window manager which is in this process. enforceStatusBarService(); - final int unknownFlags = flags & ~DISABLE_MASK; + final int unknownFlags = flags & ~StatusBarManager.DISABLE_MASK; if (unknownFlags != 0) { Slog.e(TAG, "Unknown disable flags: 0x" + Integer.toHexString(unknownFlags), new RuntimeException()); @@ -1394,8 +1388,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D if (SPEW) Slog.d(TAG, "setDisableFlags(0x" + Integer.toHexString(flags) + ")"); synchronized (mLock) { - disableLocked(displayId, mCurrentUserId, flags, mSysUiVisToken, cause, 1, - "setDisableFlags"); + disableLocked(displayId, mCurrentUserId, flags, mSysUiVisToken, cause, 1); } } @@ -2450,8 +2443,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D // ================================================================================ // lock on mDisableRecords - void manageDisableListLocked(int userId, int what, IBinder token, String pkg, int which, - String reason) { + void manageDisableListLocked(int userId, int what, IBinder token, String pkg, int which) { if (SPEW) { Slog.d(TAG, "manageDisableList userId=" + userId + " what=0x" + Integer.toHexString(what) + " pkg=" + pkg); @@ -2473,7 +2465,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D // Update existing record if (record != null) { - record.setFlags(what, which, pkg, reason); + record.setFlags(what, which, pkg); if (record.isEmpty()) { mDisableRecords.remove(i); record.token.unlinkToDeath(record, 0); @@ -2483,7 +2475,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D // Record doesn't exist, so we create a new one record = new DisableRecord(userId, token); - record.setFlags(what, which, pkg, reason); + record.setFlags(what, which, pkg); mDisableRecords.add(record); } diff --git a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java index adb55b41cb48..d6bf02fcdc47 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java +++ b/services/core/java/com/android/server/statusbar/StatusBarShellCommand.java @@ -16,6 +16,8 @@ package com.android.server.statusbar; import static android.app.StatusBarManager.DEFAULT_SETUP_DISABLE2_FLAGS; import static android.app.StatusBarManager.DEFAULT_SETUP_DISABLE_FLAGS; +import static android.app.StatusBarManager.DISABLE2_NONE; +import static android.app.StatusBarManager.DISABLE_NONE; import android.app.StatusBarManager.DisableInfo; import android.content.ComponentName; @@ -25,6 +27,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.ShellCommand; import android.service.quicksettings.TileService; +import android.util.Pair; import java.io.PrintWriter; @@ -141,17 +144,25 @@ public class StatusBarShellCommand extends ShellCommand { String arg = getNextArgRequired(); String pkg = mContext.getPackageName(); boolean disable = Boolean.parseBoolean(arg); - int userId = Binder.getCallingUserHandle().getIdentifier(); - DisableInfo info = disable ? new DisableInfo(DEFAULT_SETUP_DISABLE_FLAGS, - DEFAULT_SETUP_DISABLE2_FLAGS) : new DisableInfo(); - mInterface.disableForUser(info, sToken, pkg, userId, "runDisableForSetup"); + + if (disable) { + mInterface.disable(DEFAULT_SETUP_DISABLE_FLAGS, sToken, pkg); + mInterface.disable2(DEFAULT_SETUP_DISABLE2_FLAGS, sToken, pkg); + } else { + mInterface.disable(DISABLE_NONE, sToken, pkg); + mInterface.disable2(DISABLE2_NONE, sToken, pkg); + } + return 0; } private int runSendDisableFlag() { String pkg = mContext.getPackageName(); - int userId = Binder.getCallingUserHandle().getIdentifier(); + int disable1 = DISABLE_NONE; + int disable2 = DISABLE2_NONE; + DisableInfo info = new DisableInfo(); + String arg = getNextArg(); while (arg != null) { switch (arg) { @@ -159,7 +170,7 @@ public class StatusBarShellCommand extends ShellCommand { info.setSearchDisabled(true); break; case "home": - info.setNavigationHomeDisabled(true); + info.setNagivationHomeDisabled(true); break; case "recents": info.setRecentsDisabled(true); @@ -186,7 +197,10 @@ public class StatusBarShellCommand extends ShellCommand { arg = getNextArg(); } - mInterface.disableForUser(info, sToken, pkg, userId, "Shell Commands"); + Pair<Integer, Integer> flagPair = info.toFlags(); + + mInterface.disable(flagPair.first, sToken, pkg); + mInterface.disable2(flagPair.second, sToken, pkg); return 0; } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index e814f17bedef..21e4c967a995 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -10862,8 +10862,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final Rect filledContainerBounds = mIsInFixedOrientationOrAspectRatioLetterbox ? letterboxedContainerBounds : task != null ? task.getBounds() : display.getBounds(); - final int filledContainerRotation = task != null - ? task.getConfiguration().windowConfiguration.getRotation() + final boolean useActivityRotation = container.hasFixedRotationTransform() + && mIsInFixedOrientationOrAspectRatioLetterbox; + final int filledContainerRotation = useActivityRotation + ? container.getWindowConfiguration().getRotation() : display.getConfiguration().windowConfiguration.getRotation(); final Point dimensions = getRotationZeroDimensions( filledContainerBounds, filledContainerRotation); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 5e95a4b79d00..237003a5fa10 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -184,6 +184,7 @@ import android.content.LocusId; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; +import android.content.pm.FeatureInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -266,6 +267,7 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalManagerRegistry; import com.android.server.LocalServices; +import com.android.server.SystemConfig; import com.android.server.SystemService; import com.android.server.SystemServiceManager; import com.android.server.UiThread; @@ -7400,7 +7402,25 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + /** Cache the return value for {@link #isPip2ExperimentEnabled()} */ + private static Boolean sIsPip2ExperimentEnabled = null; + + /** + * @return {@code true} if PiP2 implementation should be used. Besides the trunk stable flag, + * system property can be used to override this read only flag during development. + * It's currently limited to phone form factor, i.e., not enabled on ARC / TV. + */ static boolean isPip2ExperimentEnabled() { - return Flags.enablePip2Implementation(); + if (sIsPip2ExperimentEnabled == null) { + final FeatureInfo arcFeature = SystemConfig.getInstance().getAvailableFeatures().get( + "org.chromium.arc"); + final FeatureInfo tvFeature = SystemConfig.getInstance().getAvailableFeatures().get( + FEATURE_LEANBACK); + final boolean isArc = arcFeature != null && arcFeature.version >= 0; + final boolean isTv = tvFeature != null && tvFeature.version >= 0; + sIsPip2ExperimentEnabled = SystemProperties.getBoolean("wm_shell.pip2", false) + || (Flags.enablePip2Implementation() && !isArc && !isTv); + } + return sIsPip2ExperimentEnabled; } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java index c74284e5976c..f06d3af49768 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java @@ -1704,6 +1704,15 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { final Transition transit = task.mTransitionController.requestCloseTransitionIfNeeded(task); if (transit != null) { transit.collectClose(task); + if (!task.mTransitionController.useFullReadyTracking()) { + // If a transition was created here, it means this is an isolated removeTask. It's + // possible for there to be no consequent operations (eg. this is a multiwindow task + // closing so nothing becomes visible in response) so we must "touch" the old ready + // tracker so that it doesn't get stuck. However, since the old ready tracker + // doesn't support multiple conditions, we have to touch it here at the beginning + // before anything that may need it to wait (setReady(false)). + transit.setReady(task, true); + } } else if (task.mTransitionController.isCollecting()) { task.mTransitionController.getCollectingTransition().collectClose(task); } diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java index 25885ed7e09a..e8faff621165 100644 --- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java +++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java @@ -96,6 +96,7 @@ class BLASTSyncEngine { interface TransactionReadyListener { void onTransactionReady(int mSyncId, SurfaceControl.Transaction transaction); default void onTransactionCommitTimeout() {} + default void onReadyTimeout() {} } /** @@ -410,6 +411,7 @@ class BLASTSyncEngine { if (allFinished && !mReady) { Slog.w(TAG, "Sync group " + mSyncId + " timed-out because not ready. If you see " + "this, please file a bug."); + mListener.onReadyTimeout(); } finishNow(); removeFromDependencies(this); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 414724963c76..c9a5e71ef1fd 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -550,15 +550,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // TODO(multi-display): remove some of the usages. boolean isDefaultDisplay; - /** Detect user tapping outside of current focused task bounds .*/ - // TODO(b/315321016): Remove once pointer event detection is removed from WM. - @VisibleForTesting - final TaskTapPointerEventListener mTapDetector; - - /** Detect user tapping outside of current focused root task bounds .*/ - // TODO(b/315321016): Remove once pointer event detection is removed from WM. - private Region mTouchExcludeRegion = new Region(); - /** Save allocating when calculating rects */ private final Rect mTmpRect = new Rect(); private final Rect mTmpRect2 = new Rect(); @@ -571,10 +562,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final PinnedTaskController mPinnedTaskController; - final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>(); - /** A collection of windows that provide tap exclude regions inside of them. */ - final ArraySet<WindowState> mTapExcludeProvidingWindows = new ArraySet<>(); - private final LinkedList<ActivityRecord> mTmpUpdateAllDrawn = new LinkedList(); private final TaskForResizePointSearchResult mTmpTaskForResizePointSearchResult = @@ -1193,18 +1180,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp "PointerEventDispatcher" + mDisplayId, mDisplayId); mPointerEventDispatcher = new PointerEventDispatcher(inputChannel); - if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) { - mTapDetector = null; - } else { - // Tap Listeners are supported for: - // 1. All physical displays (multi-display). - // 2. VirtualDisplays on VR, AA (and everything else). - mTapDetector = new TaskTapPointerEventListener(mWmService, this); - registerPointerEventListener(mTapDetector); - } - if (mWmService.mMousePositionTracker != null) { - registerPointerEventListener(mWmService.mMousePositionTracker); - } if (mWmService.mAtmService.getRecentTasks() != null) { registerPointerEventListener( mWmService.mAtmService.getRecentTasks().getInputListener()); @@ -3304,117 +3279,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mTmpTaskForResizePointSearchResult.process(taskDisplayArea, x, y, delta)); } - void updateTouchExcludeRegion() { - if (mTapDetector == null) { - // The touch exclude region is used to detect the region outside of the focused task - // so that the tap detector can detect outside touches. Don't calculate the exclude - // region when the tap detector is disabled. - return; - } - final Task focusedTask = (mFocusedApp != null ? mFocusedApp.getTask() : null); - if (focusedTask == null) { - mTouchExcludeRegion.setEmpty(); - } else { - mTouchExcludeRegion.set(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight); - final int delta = dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics); - mTmpRect.setEmpty(); - mTmpRect2.setEmpty(); - - forAllTasks(t -> { processTaskForTouchExcludeRegion(t, focusedTask, delta); }); - - // If we removed the focused task above, add it back and only leave its - // outside touch area in the exclusion. TapDetector is not interested in - // any touch inside the focused task itself. - if (!mTmpRect2.isEmpty()) { - mTouchExcludeRegion.op(mTmpRect2, Region.Op.UNION); - } - } - if (mInputMethodWindow != null && mInputMethodWindow.isVisible()) { - // If the input method is visible and the user is typing, we don't want these touch - // events to be intercepted and used to change focus. This would likely cause a - // disappearance of the input method. - mInputMethodWindow.getTouchableRegion(mTmpRegion); - mTouchExcludeRegion.op(mTmpRegion, Op.UNION); - } - for (int i = mTapExcludedWindows.size() - 1; i >= 0; i--) { - final WindowState win = mTapExcludedWindows.get(i); - if (!win.isVisible()) { - continue; - } - win.getTouchableRegion(mTmpRegion); - mTouchExcludeRegion.op(mTmpRegion, Region.Op.UNION); - } - amendWindowTapExcludeRegion(mTouchExcludeRegion); - mTapDetector.setTouchExcludeRegion(mTouchExcludeRegion); - } - - private void processTaskForTouchExcludeRegion(Task task, Task focusedTask, int delta) { - if (mTapDetector == null) { - // The touch exclude region is used to detect the region outside of the focused task - // so that the tap detector can detect outside touches. Don't calculate the exclude - // region when the tap detector is disabled. - } - final ActivityRecord topVisibleActivity = task.getTopVisibleActivity(); - - if (topVisibleActivity == null || !topVisibleActivity.hasContentToDisplay()) { - return; - } - - // Exclusion region is the region that TapDetector doesn't care about. - // Here we want to remove all non-focused tasks from the exclusion region. - // We also remove the outside touch area for resizing for all freeform - // tasks (including the focused). - // We save the focused task region once we find it, and add it back at the end. - // If the task is root home task and it is resizable and visible (top of its root task), - // we want to exclude the root docked task from touch so we need the entire screen area - // and not just a small portion which the root home task currently is resized to. - if (task.isActivityTypeHome() && task.isVisible() && task.isResizeable()) { - task.getDisplayArea().getBounds(mTmpRect); - } else { - task.getDimBounds(mTmpRect); - } - - if (task == focusedTask) { - // Add the focused task rect back into the exclude region once we are done - // processing root tasks. - // NOTE: this *looks* like a no-op, but this usage of mTmpRect2 is expected by - // updateTouchExcludeRegion. - mTmpRect2.set(mTmpRect); - } - - final boolean isFreeformed = task.inFreeformWindowingMode(); - if (task != focusedTask || isFreeformed) { - if (isFreeformed) { - // If the task is freeformed, enlarge the area to account for outside - // touch area for resize. - mTmpRect.inset(-delta, -delta); - // Intersect with display content frame. If we have system decor (status bar/ - // navigation bar), we want to exclude that from the tap detection. - // Otherwise, if the app is partially placed under some system button (eg. - // Recents, Home), pressing that button would cause a full series of - // unwanted transfer focus/resume/pause, before we could go home. - mTmpRect.inset(getInsetsStateController().getRawInsetsState().calculateInsets( - mTmpRect, systemBars() | ime(), false /* ignoreVisibility */)); - } - mTouchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE); - } - } - - /** - * Union the region with all the tap exclude region provided by windows on this display. - * - * @param inOutRegion The region to be amended. - */ - private void amendWindowTapExcludeRegion(Region inOutRegion) { - final Region region = Region.obtain(); - for (int i = mTapExcludeProvidingWindows.size() - 1; i >= 0; i--) { - final WindowState win = mTapExcludeProvidingWindows.valueAt(i); - win.getTapExcludeRegion(region); - inOutRegion.op(region, Op.UNION); - } - region.recycle(); - } - @Override void switchUser(int userId) { super.switchUser(userId); @@ -3771,7 +3635,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight); pw.print(subPrefix + "deferred=" + mDeferredRemoval + " mLayoutNeeded=" + mLayoutNeeded); - pw.println(" mTouchExcludeRegion=" + mTouchExcludeRegion); pw.println(); super.dump(pw, prefix, dumpAll); @@ -4120,7 +3983,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } getInputMonitor().setFocusedAppLw(newFocus); - updateTouchExcludeRegion(); return true; } diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java index 8116f6870f66..30f2d0d64d13 100644 --- a/services/core/java/com/android/server/wm/DragDropController.java +++ b/services/core/java/com/android/server/wm/DragDropController.java @@ -21,13 +21,11 @@ import static android.view.View.DRAG_FLAG_GLOBAL; import static android.view.View.DRAG_FLAG_GLOBAL_SAME_APPLICATION; import static android.view.View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG; -import static com.android.input.flags.Flags.enablePointerChoreographer; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.NonNull; -import android.app.ActivityManager; import android.content.ClipData; import android.content.Context; import android.hardware.input.InputManagerGlobal; @@ -266,16 +264,12 @@ class DragDropController { final SurfaceControl surfaceControl = mDragState.mSurfaceControl; mDragState.broadcastDragStartedLocked(touchX, touchY); - if (enablePointerChoreographer()) { - if ((touchSource & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) { - InputManagerGlobal.getInstance().setPointerIcon( - PointerIcon.getSystemIcon( - mService.mContext, PointerIcon.TYPE_GRABBING), - mDragState.mDisplayContent.getDisplayId(), touchDeviceId, - touchPointerId, mDragState.getInputToken()); - } - } else { - mDragState.overridePointerIconLocked(touchSource); + if ((touchSource & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) { + InputManagerGlobal.getInstance().setPointerIcon( + PointerIcon.getSystemIcon( + mService.mContext, PointerIcon.TYPE_GRABBING), + mDragState.mDisplayContent.getDisplayId(), touchDeviceId, + touchPointerId, mDragState.getInputToken()); } // remember the thumb offsets for later mDragState.mThumbOffsetX = thumbCenterX; diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index 5ed343a4d028..72ae64c455fe 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -45,7 +45,6 @@ import android.content.ClipData; import android.content.ClipDescription; import android.graphics.Point; import android.graphics.Rect; -import android.hardware.input.InputManagerGlobal; import android.os.Binder; import android.os.Build; import android.os.IBinder; @@ -58,9 +57,7 @@ import android.view.Display; import android.view.DragEvent; import android.view.InputApplicationHandle; import android.view.InputChannel; -import android.view.InputDevice; import android.view.InputWindowHandle; -import android.view.PointerIcon; import android.view.SurfaceControl; import android.view.View; import android.view.WindowManager; @@ -110,7 +107,6 @@ class DragState { boolean mCrossProfileCopyAllowed; ClipData mData; ClipDescription mDataDescription; - int mTouchSource; boolean mDragResult; boolean mRelinquishDragSurfaceToDropTarget; float mAnimatedScale = 1.0f; @@ -263,12 +259,6 @@ class DragState { Trace.instant(TRACE_TAG_WINDOW_MANAGER, "DragDropController#DRAG_ENDED"); } - // Take the cursor back if it has been changed. - if (isFromSource(InputDevice.SOURCE_MOUSE)) { - mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY); - mTouchSource = 0; - } - // Clear the internal variables. if (mInputSurface != null) { mTransaction.remove(mInputSurface).apply(); @@ -762,18 +752,6 @@ class DragState { return animator; } - private boolean isFromSource(int source) { - return (mTouchSource & source) == source; - } - - void overridePointerIconLocked(int touchSource) { - mTouchSource = touchSource; - if (isFromSource(InputDevice.SOURCE_MOUSE)) { - // TODO(b/293587049): Pointer Icon Refactor: Set the pointer icon from the drag window. - InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_GRABBING); - } - } - private class AnimationListener implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener { @Override diff --git a/services/core/java/com/android/server/wm/InputConfigAdapter.java b/services/core/java/com/android/server/wm/InputConfigAdapter.java index 119fafde6f77..ae6e72464555 100644 --- a/services/core/java/com/android/server/wm/InputConfigAdapter.java +++ b/services/core/java/com/android/server/wm/InputConfigAdapter.java @@ -20,8 +20,6 @@ import android.os.InputConfig; import android.view.InputWindowHandle.InputConfigFlags; import android.view.WindowManager.LayoutParams; -import java.util.List; - /** * A helper to determine the {@link InputConfigFlags} that control the behavior of an input window * from several WM attributes. @@ -47,7 +45,7 @@ class InputConfigAdapter { * input configurations that can be mapped directly from a corresponding LayoutParams input * feature. */ - private static final List<FlagMapping> INPUT_FEATURE_TO_CONFIG_MAP = List.of( + private static final FlagMapping[] INPUT_FEATURE_TO_CONFIG_MAP = { new FlagMapping( LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL, InputConfig.NO_INPUT_CHANNEL, false /* inverted */), @@ -59,7 +57,8 @@ class InputConfigAdapter { InputConfig.SPY, false /* inverted */), new FlagMapping( LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_PRIVACY, - InputConfig.SENSITIVE_FOR_PRIVACY, false /* inverted */)); + InputConfig.SENSITIVE_FOR_PRIVACY, false /* inverted */) + }; @InputConfigFlags private static final int INPUT_FEATURE_TO_CONFIG_MASK = @@ -72,7 +71,7 @@ class InputConfigAdapter { * NOTE: The layout params flag {@link LayoutParams#FLAG_NOT_FOCUSABLE} is not handled by this * adapter, and must be handled explicitly. */ - private static final List<FlagMapping> LAYOUT_PARAM_FLAG_TO_CONFIG_MAP = List.of( + private static final FlagMapping[] LAYOUT_PARAM_FLAG_TO_CONFIG_MAP = { new FlagMapping( LayoutParams.FLAG_NOT_TOUCHABLE, InputConfig.NOT_TOUCHABLE, false /* inverted */), @@ -84,7 +83,8 @@ class InputConfigAdapter { InputConfig.WATCH_OUTSIDE_TOUCH, false /* inverted */), new FlagMapping( LayoutParams.FLAG_SLIPPERY, - InputConfig.SLIPPERY, false /* inverted */)); + InputConfig.SLIPPERY, false /* inverted */) + }; @InputConfigFlags private static final int LAYOUT_PARAM_FLAG_TO_CONFIG_MASK = @@ -119,7 +119,7 @@ class InputConfigAdapter { } @InputConfigFlags - private static int applyMapping(int flags, List<FlagMapping> flagToConfigMap) { + private static int applyMapping(int flags, FlagMapping[] flagToConfigMap) { int inputConfig = 0; for (final FlagMapping mapping : flagToConfigMap) { final boolean flagSet = (flags & mapping.mFlag) != 0; @@ -131,7 +131,7 @@ class InputConfigAdapter { } @InputConfigFlags - private static int computeMask(List<FlagMapping> flagToConfigMap) { + private static int computeMask(FlagMapping[] flagToConfigMap) { int mask = 0; for (final FlagMapping mapping : flagToConfigMap) { mask |= mapping.mInputConfig; diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index a84ebd95cf1c..22ca82a29d59 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -290,22 +290,6 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal } } - @Override - public void notifyPointerDisplayIdChanged(int displayId, float x, float y) { - synchronized (mService.mGlobalLock) { - mService.setMousePointerDisplayId(displayId); - if (displayId == Display.INVALID_DISPLAY) return; - - final DisplayContent dc = mService.mRoot.getDisplayContent(displayId); - if (dc == null) { - Slog.wtf(TAG, "The mouse pointer was moved to display " + displayId - + " that does not have a valid DisplayContent."); - return; - } - mService.restorePointerIconLocked(dc, x, y); - } - } - /** 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/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index ce1a72deb523..b5af8065726d 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; +import android.annotation.DimenRes; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -266,10 +267,10 @@ final class LetterboxConfiguration { private boolean mIsDisplayAspectRatioEnabledForFixedOrientationLetterbox; // Supplier for the value in pixel to consider when detecting vertical thin letterboxing - private final IntSupplier mThinLetterboxWidthFn; + private final DimenPxIntSupplier mThinLetterboxWidthPxSupplier; // Supplier for the value in pixel to consider when detecting horizontal thin letterboxing - private final IntSupplier mThinLetterboxHeightFn; + private final DimenPxIntSupplier mThinLetterboxHeightPxSupplier; // Allows to enable letterboxing strategy for translucent activities ignoring flags. private boolean mTranslucentLetterboxingOverrideEnabled; @@ -307,6 +308,34 @@ final class LetterboxConfiguration { // Flags dynamically updated with {@link android.provider.DeviceConfig}. @NonNull private final SynchedDeviceConfig mDeviceConfig; + // Cached version of IntSupplier customised to evaluate new dimen in pixels + // when density changes + private static class DimenPxIntSupplier implements IntSupplier { + + @NonNull + private final Context mContext; + + private final int mResourceId; + + private float mLastDensity = Float.MIN_VALUE; + private int mValue = 0; + + private DimenPxIntSupplier(@NonNull Context context, @DimenRes int resourceId) { + mContext = context; + mResourceId = resourceId; + } + + @Override + public int getAsInt() { + final float newDensity = mContext.getResources().getDisplayMetrics().density; + if (newDensity != mLastDensity) { + mLastDensity = newDensity; + mValue = mContext.getResources().getDimensionPixelSize(mResourceId); + } + return mValue; + } + } + LetterboxConfiguration(@NonNull final Context systemUiContext) { this(systemUiContext, new LetterboxConfigurationPersister( () -> readLetterboxHorizontalReachabilityPositionFromConfig( @@ -364,9 +393,10 @@ final class LetterboxConfiguration { R.bool.config_isWindowManagerCameraCompatSplitScreenAspectRatioEnabled); mIsPolicyForIgnoringRequestedOrientationEnabled = mContext.getResources().getBoolean( R.bool.config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled); - mThinLetterboxWidthFn = () -> mContext.getResources().getDimensionPixelSize( + + mThinLetterboxWidthPxSupplier = new DimenPxIntSupplier(mContext, R.dimen.config_letterboxThinLetterboxWidthDp); - mThinLetterboxHeightFn = () -> mContext.getResources().getDimensionPixelSize( + mThinLetterboxHeightPxSupplier = new DimenPxIntSupplier(mContext, R.dimen.config_letterboxThinLetterboxHeightDp); mLetterboxConfigurationPersister = letterboxConfigurationPersister; @@ -1144,7 +1174,7 @@ final class LetterboxConfiguration { * is the maximum value for (W - w) / 2 to be considered for a thin letterboxed app. */ int getThinLetterboxWidthPx() { - return mThinLetterboxWidthFn.getAsInt(); + return mThinLetterboxWidthPxSupplier.getAsInt(); } /** @@ -1153,7 +1183,7 @@ final class LetterboxConfiguration { * value for (H - h) / 2 to be considered for a thin letterboxed app. */ int getThinLetterboxHeightPx() { - return mThinLetterboxHeightFn.getAsInt(); + return mThinLetterboxHeightPxSupplier.getAsInt(); } /** diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 9e16b8abe0de..57827c567b5b 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -989,6 +989,10 @@ final class LetterboxUiController { } } + boolean isLetterboxEducationEnabled() { + return mLetterboxConfiguration.getIsEducationEnabled(); + } + /** * Whether we use split screen aspect ratio for the activity when camera compat treatment * is active because the corresponding config is enabled and activity supports resizing. diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 6dec71260fa7..72f592b577da 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -64,7 +64,6 @@ import android.os.Environment; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; -import android.os.SystemProperties; import android.os.UserHandle; import android.text.TextUtils; import android.util.ArraySet; diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 6003c1b897b4..be8c2aea7ad1 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -911,7 +911,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> dc.getInputMonitor().updateInputWindowsLw(true /*force*/); dc.updateSystemGestureExclusion(); dc.updateKeepClearAreas(); - dc.updateTouchExcludeRegion(); }); // Check to see if we are now in a state where the screen should diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index bb86460572af..3b3eeb496ab7 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -740,16 +740,6 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } @Override - public void updatePointerIcon(IWindow window) { - final long identity = Binder.clearCallingIdentity(); - try { - mService.updatePointerIcon(window); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - - @Override public void updateTapExcludeRegion(IWindow window, Region region) { final long identity = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 8bd7b5f78cf4..8defec3dbeab 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3448,6 +3448,8 @@ class Task extends TaskFragment { // Whether the direct top activity is eligible for letterbox education. appCompatTaskInfo.topActivityEligibleForLetterboxEducation = isTopActivityResumed && top.isEligibleForLetterboxEducation(); + appCompatTaskInfo.isLetterboxEducationEnabled = top != null + && top.mLetterboxUiController.isLetterboxEducationEnabled(); // Whether the direct top activity requested showing camera compat control. appCompatTaskInfo.cameraCompatTaskInfo.cameraCompatControlState = isTopActivityResumed ? top.getCameraCompatControlState() diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java deleted file mode 100644 index ac244c7b048e..000000000000 --- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS 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.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW; -import static android.view.PointerIcon.TYPE_NOT_SPECIFIED; -import static android.view.PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW; -import static android.view.PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW; -import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW; - -import android.graphics.Rect; -import android.graphics.Region; -import android.hardware.input.InputManagerGlobal; -import android.view.InputDevice; -import android.view.MotionEvent; -import android.view.WindowManagerPolicyConstants.PointerEventListener; - -import com.android.server.wm.WindowManagerService.H; - -/** - * 1. Adjust the top most focus display if touch down on some display. - * 2. Adjust the pointer icon when cursor moves to the task bounds. - */ -public class TaskTapPointerEventListener implements PointerEventListener { - - private final Region mTouchExcludeRegion = new Region(); - private final WindowManagerService mService; - private final DisplayContent mDisplayContent; - private final Rect mTmpRect = new Rect(); - private int mPointerIconType = TYPE_NOT_SPECIFIED; - - public TaskTapPointerEventListener(WindowManagerService service, - DisplayContent displayContent) { - // TODO(b/315321016): Remove this class when the flag rollout is complete. - if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) { - throw new IllegalStateException("TaskTapPointerEventListener should not be used!"); - } - mService = service; - mDisplayContent = displayContent; - } - - private void restorePointerIcon(int x, int y) { - if (mPointerIconType != TYPE_NOT_SPECIFIED) { - mPointerIconType = TYPE_NOT_SPECIFIED; - // Find the underlying window and ask it to restore the pointer icon. - mService.mH.removeMessages(H.RESTORE_POINTER_ICON); - mService.mH.obtainMessage(H.RESTORE_POINTER_ICON, - x, y, mDisplayContent).sendToTarget(); - } - } - - @Override - public void onPointerEvent(MotionEvent motionEvent) { - switch (motionEvent.getActionMasked()) { - case MotionEvent.ACTION_DOWN: { - final int x; - final int y; - if (motionEvent.getSource() == InputDevice.SOURCE_MOUSE) { - x = (int) motionEvent.getXCursorPosition(); - y = (int) motionEvent.getYCursorPosition(); - } else { - x = (int) motionEvent.getX(); - y = (int) motionEvent.getY(); - } - - synchronized (this) { - if (!mTouchExcludeRegion.contains(x, y)) { - mService.mTaskPositioningController.handleTapOutsideTask( - mDisplayContent, x, y); - } - } - } - break; - case MotionEvent.ACTION_HOVER_ENTER: - case MotionEvent.ACTION_HOVER_MOVE: { - final int x = (int) motionEvent.getX(); - final int y = (int) motionEvent.getY(); - if (mTouchExcludeRegion.contains(x, y)) { - restorePointerIcon(x, y); - break; - } - final Task task = mDisplayContent.findTaskForResizePoint(x, y); - int iconType = TYPE_NOT_SPECIFIED; - if (task != null) { - task.getDimBounds(mTmpRect); - if (!mTmpRect.isEmpty() && !mTmpRect.contains(x, y)) { - if (x < mTmpRect.left) { - iconType = - (y < mTmpRect.top) ? TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW : - (y > mTmpRect.bottom) ? TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW : - TYPE_HORIZONTAL_DOUBLE_ARROW; - } else if (x > mTmpRect.right) { - iconType = - (y < mTmpRect.top) ? TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW : - (y > mTmpRect.bottom) ? TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW : - TYPE_HORIZONTAL_DOUBLE_ARROW; - } else if (y < mTmpRect.top || y > mTmpRect.bottom) { - iconType = TYPE_VERTICAL_DOUBLE_ARROW; - } - } - } - if (mPointerIconType != iconType) { - mPointerIconType = iconType; - if (mPointerIconType == TYPE_NOT_SPECIFIED) { - // Find the underlying window and ask it restore the pointer icon. - mService.mH.removeMessages(H.RESTORE_POINTER_ICON); - mService.mH.obtainMessage(H.RESTORE_POINTER_ICON, - x, y, mDisplayContent).sendToTarget(); - } else { - InputManagerGlobal.getInstance() - .setPointerIconType(mPointerIconType); - } - } - } - break; - case MotionEvent.ACTION_HOVER_EXIT: { - final int x = (int) motionEvent.getX(); - final int y = (int) motionEvent.getY(); - restorePointerIcon(x, y); - } - break; - } - } - - void setTouchExcludeRegion(Region newRegion) { - synchronized (this) { - mTouchExcludeRegion.set(newRegion); - } - } -} diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 1543263c0e89..7ec31d5a8ecb 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -1648,14 +1648,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } if (mController.useFullReadyTracking()) { - if (mReadyTracker.mMet.isEmpty()) { - Slog.e(TAG, "#" + mSyncId + ": No conditions provided"); - } else { - for (int i = 0; i < mReadyTracker.mConditions.size(); ++i) { - Slog.e(TAG, "#" + mSyncId + ": unmet condition at ready: " - + mReadyTracker.mConditions.get(i)); - } - } for (int i = 0; i < mReadyTracker.mMet.size(); ++i) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "#%d: Met condition: %s", mSyncId, mReadyTracker.mMet.get(i)); @@ -3360,6 +3352,18 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { applyReady(); } + @Override + public void onReadyTimeout() { + if (!mController.useFullReadyTracking()) { + Slog.e(TAG, "#" + mSyncId + " readiness timeout, used=" + mReadyTrackerOld.mUsed + + " deferReadyDepth=" + mReadyTrackerOld.mDeferReadyDepth + + " group=" + mReadyTrackerOld.mReadyGroups); + return; + } + Slog.e(TAG, "#" + mSyncId + " met conditions: " + mReadyTracker.mMet); + Slog.e(TAG, "#" + mSyncId + " unmet conditions: " + mReadyTracker.mConditions); + } + /** * Represents a condition that must be met before an associated transition can be considered * ready. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index dbe3d369db7d..e02e5bef688c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -81,15 +81,11 @@ import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; -import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; -import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION; import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION; import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG; -import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; -import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED; import static android.view.WindowManager.TRANSIT_NONE; @@ -203,7 +199,6 @@ import android.hardware.configstore.V1_0.OptionalBool; import android.hardware.configstore.V1_1.ISurfaceFlingerConfigs; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; -import android.hardware.input.InputManager; import android.hardware.input.InputSettings; import android.net.Uri; import android.os.Binder; @@ -289,8 +284,6 @@ import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.KeyEvent; import android.view.MagnificationSpec; -import android.view.MotionEvent; -import android.view.PointerIcon; import android.view.RemoteAnimationAdapter; import android.view.ScrollCaptureResponse; import android.view.Surface; @@ -335,6 +328,7 @@ import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; +import com.android.internal.os.TransferPipe; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IKeyguardLockedStateListener; import com.android.internal.policy.IShortcutService; @@ -545,13 +539,16 @@ public class WindowManagerService extends IWindowManager.Stub if (asProto) { return; } + + final long timeoutMs = 1000L; mAtmService.dumpActivity(fd, pw, /* name= */ "all", /* args= */ new String[]{}, /* opti= */ 0, /* dumpAll= */ true, /* dumpVisibleRootTasksOnly= */ true, /* dumpFocusedRootTaskOnly= */ false, INVALID_DISPLAY, UserHandle.USER_ALL, - /* timeout= */ 1000 + timeoutMs ); + dumpVisibleWindowClients(fd, pw, timeoutMs); } @Override @@ -1523,18 +1520,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - static boolean excludeWindowTypeFromTapOutTask(int windowType) { - switch (windowType) { - case TYPE_STATUS_BAR: - case TYPE_NOTIFICATION_SHADE: - case TYPE_NAVIGATION_BAR: - case TYPE_INPUT_METHOD_DIALOG: - case TYPE_VOLUME_OVERLAY: - return true; - } - return false; - } - public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility, int displayId, int requestUserId, @InsetsType int requestedVisibleTypes, InputChannel outInputChannel, InsetsState outInsetsState, @@ -1833,10 +1818,6 @@ public class WindowManagerService extends IWindowManager.Stub displayContent.mWinAddedSinceNullFocus.add(win); } - if (excludeWindowTypeFromTapOutTask(type)) { - displayContent.mTapExcludedWindows.add(win); - } - win.mSession.onWindowAdded(win); mWindowMap.put(client.asBinder(), win); win.initAppOpsState(); @@ -5716,7 +5697,6 @@ public class WindowManagerService extends IWindowManager.Stub public static final int UPDATE_ANIMATION_SCALE = 51; public static final int WINDOW_HIDE_TIMEOUT = 52; - public static final int RESTORE_POINTER_ICON = 55; public static final int SET_HAS_OVERLAY_UI = 58; public static final int ANIMATION_FAILSAFE = 60; public static final int RECOMPUTE_FOCUS = 61; @@ -5949,12 +5929,6 @@ public class WindowManagerService extends IWindowManager.Stub } break; } - case RESTORE_POINTER_ICON: { - synchronized (mGlobalLock) { - restorePointerIconLocked((DisplayContent)msg.obj, msg.arg1, msg.arg2); - } - break; - } case SET_HAS_OVERLAY_UI: { mAmInternal.setHasOverlayUi(msg.arg1, msg.arg2 == 1); break; @@ -7574,144 +7548,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - // The mouse position tracker will be obsolete after the Pointer Icon Refactor. - // TODO(b/293587049): Remove after the refactoring is fully rolled out. - @Nullable - final MousePositionTracker mMousePositionTracker = - com.android.input.flags.Flags.enablePointerChoreographer() ? null - : new MousePositionTracker(); - - private static class MousePositionTracker implements PointerEventListener { - private boolean mLatestEventWasMouse; - private float mLatestMouseX; - private float mLatestMouseY; - - /** - * The display that the pointer (mouse cursor) is currently shown on. This is updated - * directly by InputManagerService when the pointer display changes. - */ - private int mPointerDisplayId = INVALID_DISPLAY; - - /** - * Update the mouse cursor position as a result of a mouse movement. - * @return true if the position was successfully updated, false otherwise. - */ - boolean updatePosition(int displayId, float x, float y) { - synchronized (this) { - mLatestEventWasMouse = true; - - if (displayId != mPointerDisplayId) { - // The display of the position update does not match the display on which the - // mouse pointer is shown, so do not update the position. - return false; - } - mLatestMouseX = x; - mLatestMouseY = y; - return true; - } - } - - void setPointerDisplayId(int displayId) { - synchronized (this) { - mPointerDisplayId = displayId; - } - } - - @Override - public void onPointerEvent(MotionEvent motionEvent) { - if (motionEvent.isFromSource(InputDevice.SOURCE_MOUSE)) { - updatePosition(motionEvent.getDisplayId(), motionEvent.getRawX(), - motionEvent.getRawY()); - } else { - synchronized (this) { - mLatestEventWasMouse = false; - } - } - } - }; - - void updatePointerIcon(IWindow client) { - if (mMousePositionTracker == null) { - return; - } - int pointerDisplayId; - float mouseX, mouseY; - - synchronized(mMousePositionTracker) { - if (!mMousePositionTracker.mLatestEventWasMouse) { - return; - } - mouseX = mMousePositionTracker.mLatestMouseX; - mouseY = mMousePositionTracker.mLatestMouseY; - pointerDisplayId = mMousePositionTracker.mPointerDisplayId; - } - - synchronized (mGlobalLock) { - if (mDragDropController.dragDropActiveLocked()) { - // Drag cursor overrides the app cursor. - return; - } - WindowState callingWin = windowForClientLocked(null, client, false); - if (callingWin == null) { - ProtoLog.w(WM_ERROR, "Bad requesting window %s", client); - return; - } - final DisplayContent displayContent = callingWin.getDisplayContent(); - if (displayContent == null) { - return; - } - if (pointerDisplayId != displayContent.getDisplayId()) { - // Do not let the pointer icon be updated by a window on a different display. - return; - } - WindowState windowUnderPointer = - displayContent.getTouchableWinAtPointLocked(mouseX, mouseY); - if (windowUnderPointer != callingWin) { - return; - } - try { - windowUnderPointer.mClient.updatePointerIcon( - windowUnderPointer.translateToWindowX(mouseX), - windowUnderPointer.translateToWindowY(mouseY)); - } catch (RemoteException e) { - ProtoLog.w(WM_ERROR, "unable to update pointer icon"); - } - } - } - - void restorePointerIconLocked(DisplayContent displayContent, float latestX, float latestY) { - if (mMousePositionTracker == null) { - return; - } - // Mouse position tracker has not been getting updates while dragging, update it now. - if (!mMousePositionTracker.updatePosition( - displayContent.getDisplayId(), latestX, latestY)) { - // The mouse position could not be updated, so ignore this request. - return; - } - - WindowState windowUnderPointer = - displayContent.getTouchableWinAtPointLocked(latestX, latestY); - if (windowUnderPointer != null) { - try { - windowUnderPointer.mClient.updatePointerIcon( - windowUnderPointer.translateToWindowX(latestX), - windowUnderPointer.translateToWindowY(latestY)); - } catch (RemoteException e) { - ProtoLog.w(WM_ERROR, "unable to restore pointer icon"); - } - } else { - mContext.getSystemService(InputManager.class) - .setPointerIconType(PointerIcon.TYPE_DEFAULT); - } - } - void setMousePointerDisplayId(int displayId) { - if (mMousePositionTracker == null) { - return; - } - mMousePositionTracker.setPointerDisplayId(displayId); - } - /** * Update a tap exclude region in the window identified by the provided id. Touches down on this * region will not: @@ -10350,4 +10186,32 @@ public class WindowManagerService extends IWindowManager.Stub } return true; } + + /** + * Dump ViewRootImpl for visible non-activity windows. + */ + private void dumpVisibleWindowClients(FileDescriptor fd, PrintWriter pw, long timeout) { + final ArrayList<WindowState> systemWindows = new ArrayList<>(); + synchronized (mGlobalLock) { + mRoot.forAllWindows(w -> { + if (!w.isActivityWindow() && w.isVisibleNow()) { + systemWindows.add(w); + } + }, false /* traverseTopToBottom */); + } + + systemWindows.forEach(w -> { + pw.println("---------------------------------"); + pw.println(w.toString()); + pw.flush(); + try (TransferPipe tp = new TransferPipe()) { + w.mClient.dumpWindow(tp.getWriteFd()); + tp.go(fd, timeout); + } catch (IOException e) { + pw.println("Failure while dumping the window: " + e); + } catch (RemoteException e) { + pw.println("Got a RemoteException while dumping the window"); + } + }); + } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index e90c845f0d21..8fb83fa0e88c 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1447,14 +1447,17 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP + " last=" + mWindowFrames.mLastFrame + " frame=" + mWindowFrames.mFrame); } + final boolean contentChanged = didFrameInsetsChange || configChanged + || dragResizingChanged || attachedFrameChanged; + // Cancel unchanged non-sync-buffer redraw request to avoid unnecessary reportResized(). + if (!contentChanged && !mRedrawForSyncReported && mPrepareSyncSeqId <= 0 + && mDrawHandlers.isEmpty()) { + mRedrawForSyncReported = true; + } + // Add a window that is using blastSync to the resizing list if it hasn't been reported // already. This because the window is waiting on a finishDrawing from the client. - if (didFrameInsetsChange - || configChanged - || insetsChanged - || dragResizingChanged - || shouldSendRedrawForSync() - || attachedFrameChanged) { + if (contentChanged || insetsChanged || shouldSendRedrawForSync()) { ProtoLog.v(WM_DEBUG_RESIZE, "Resize reasons for w=%s: %s configChanged=%b didFrameInsetsChange=%b", this, mWindowFrames.getInsetsChangedInfo(), @@ -2359,18 +2362,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } final int type = mAttrs.type; - if (WindowManagerService.excludeWindowTypeFromTapOutTask(type)) { - dc.mTapExcludedWindows.remove(this); - } if (type == TYPE_PRESENTATION || type == TYPE_PRIVATE_PRESENTATION) { mWmService.mDisplayManagerInternal.onPresentation(dc.getDisplay().getDisplayId(), /*isShown=*/ false); } - // Remove this window from mTapExcludeProvidingWindows. If it was not registered, this will - // not do anything. - dc.mTapExcludeProvidingWindows.remove(this); dc.getDisplayPolicy().removeWindowLw(this); disposeInputChannel(); @@ -5526,18 +5523,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Clear the tap excluded region if the region passed in is null or empty. if (region == null || region.isEmpty()) { mTapExcludeRegion.setEmpty(); - // Remove this window from mTapExcludeProvidingWindows since it won't be providing - // tap exclude regions. - currentDisplay.mTapExcludeProvidingWindows.remove(this); } else { mTapExcludeRegion.set(region); - // Make sure that this window is registered as one that provides a tap exclude region - // for its containing display. - currentDisplay.mTapExcludeProvidingWindows.add(this); } - // Trigger touch exclude region update on current display. - currentDisplay.updateTouchExcludeRegion(); // Trigger touchable region update for this window. currentDisplay.getInputMonitor().updateInputWindowsLw(true /* force */); } @@ -6074,6 +6063,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return mPrepareSyncSeqId > 0; } + public boolean isActivityWindow() { + return mActivityRecord != null; + } + void setSecureLocked(boolean isSecure) { ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isSecure=%b: %s", isSecure, getName()); if (secureWindowState()) { diff --git a/services/core/jni/com_android_server_display_DisplayControl.cpp b/services/core/jni/com_android_server_display_DisplayControl.cpp index e65b9030195a..22c0f730ad7d 100644 --- a/services/core/jni/com_android_server_display_DisplayControl.cpp +++ b/services/core/jni/com_android_server_display_DisplayControl.cpp @@ -24,9 +24,11 @@ namespace android { static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj, jboolean secure, - jfloat requestedRefreshRate) { - ScopedUtfChars name(env, nameObj); + jstring uniqueIdStr, jfloat requestedRefreshRate) { + const ScopedUtfChars name(env, nameObj); + const ScopedUtfChars uniqueId(env, uniqueIdStr); sp<IBinder> token(SurfaceComposerClient::createDisplay(String8(name.c_str()), bool(secure), + std::string(uniqueId.c_str()), requestedRefreshRate)); return javaObjectForIBinder(env, token); } @@ -178,7 +180,7 @@ static jobject nativeGetPhysicalDisplayToken(JNIEnv* env, jclass clazz, jlong ph static const JNINativeMethod sDisplayMethods[] = { // clang-format off - {"nativeCreateDisplay", "(Ljava/lang/String;ZF)Landroid/os/IBinder;", + {"nativeCreateDisplay", "(Ljava/lang/String;ZLjava/lang/String;F)Landroid/os/IBinder;", (void*)nativeCreateDisplay }, {"nativeDestroyDisplay", "(Landroid/os/IBinder;)V", (void*)nativeDestroyDisplay }, diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 62f5b89e701c..a01c1231b373 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -127,7 +127,7 @@ static struct { jmethodID getVirtualKeyQuietTimeMillis; jmethodID getExcludedDeviceNames; jmethodID getInputPortAssociations; - jmethodID getInputUniqueIdAssociations; + jmethodID getInputUniqueIdAssociationsByPort; jmethodID getInputUniqueIdAssociationsByDescriptor; jmethodID getDeviceTypeAssociations; jmethodID getKeyboardLayoutAssociations; @@ -272,22 +272,23 @@ public: void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray); base::Result<std::unique_ptr<InputChannel>> createInputChannel(const std::string& name); - base::Result<std::unique_ptr<InputChannel>> createInputMonitor(int32_t displayId, + base::Result<std::unique_ptr<InputChannel>> createInputMonitor(ui::LogicalDisplayId displayId, const std::string& name, gui::Pid pid); status_t removeInputChannel(const sp<IBinder>& connectionToken); status_t pilferPointers(const sp<IBinder>& token); - void displayRemoved(JNIEnv* env, int32_t displayId); - void setFocusedApplication(JNIEnv* env, int32_t displayId, jobject applicationHandleObj); - void setFocusedDisplay(int32_t displayId); + void displayRemoved(JNIEnv* env, ui::LogicalDisplayId displayId); + void setFocusedApplication(JNIEnv* env, ui::LogicalDisplayId displayId, + jobject applicationHandleObj); + void setFocusedDisplay(ui::LogicalDisplayId displayId); void setMinTimeBetweenUserActivityPokes(int64_t intervalMillis); void setInputDispatchMode(bool enabled, bool frozen); void setSystemUiLightsOut(bool lightsOut); - void setPointerDisplayId(int32_t displayId); + void setPointerDisplayId(ui::LogicalDisplayId displayId); int32_t getMousePointerSpeed(); void setPointerSpeed(int32_t speed); - void setMousePointerAccelerationEnabled(int32_t displayId, bool enabled); + void setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId, bool enabled); void setTouchpadPointerSpeed(int32_t speed); void setTouchpadNaturalScrollingEnabled(bool enabled); void setTouchpadTapToClickEnabled(bool enabled); @@ -300,13 +301,13 @@ public: void reloadPointerIcons(); void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled); bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, - int32_t displayId, DeviceId deviceId, int32_t pointerId, + ui::LogicalDisplayId displayId, DeviceId deviceId, int32_t pointerId, const sp<IBinder>& inputToken); - void setPointerIconVisibility(int32_t displayId, bool visible); + void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible); void setMotionClassifierEnabled(bool enabled); std::optional<std::string> getBluetoothAddress(int32_t deviceId); void setStylusButtonMotionEventsEnabled(bool enabled); - FloatPoint getMouseCursorPosition(int32_t displayId); + FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId); void setStylusPointerIconEnabled(bool enabled); void setInputMethodConnectionIsActive(bool isActive); @@ -325,7 +326,7 @@ public: void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override; bool isInputMethodConnectionActive() override; std::optional<DisplayViewport> getPointerViewportForAssociatedDisplay( - int32_t associatedDisplayId) override; + ui::LogicalDisplayId associatedDisplayId) override; /* --- InputDispatcherPolicyInterface implementation --- */ @@ -348,13 +349,15 @@ public: void notifyVibratorState(int32_t deviceId, bool isOn) override; bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override; void interceptKeyBeforeQueueing(const KeyEvent& keyEvent, uint32_t& policyFlags) override; - void interceptMotionBeforeQueueing(int32_t displayId, uint32_t source, int32_t action, - nsecs_t when, uint32_t& policyFlags) override; + void interceptMotionBeforeQueueing(ui::LogicalDisplayId displayId, uint32_t source, + int32_t action, nsecs_t when, + uint32_t& policyFlags) override; nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token, const KeyEvent& keyEvent, uint32_t policyFlags) override; std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>& token, const KeyEvent& keyEvent, uint32_t policyFlags) override; - void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override; + void pokeUserActivity(nsecs_t eventTime, int32_t eventType, + ui::LogicalDisplayId displayId) override; void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override; void setPointerCapture(const PointerCaptureRequest& request) override; void notifyDropWindow(const sp<IBinder>& token, float x, float y) override; @@ -363,11 +366,13 @@ public: /* --- PointerControllerPolicyInterface implementation --- */ - virtual void loadPointerIcon(SpriteIcon* icon, int32_t displayId); - virtual void loadPointerResources(PointerResources* outResources, int32_t displayId); + virtual void loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId); + virtual void loadPointerResources(PointerResources* outResources, + ui::LogicalDisplayId displayId); virtual void loadAdditionalMouseResources( std::map<PointerIconStyle, SpriteIcon>* outResources, - std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t displayId); + std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, + ui::LogicalDisplayId displayId); virtual PointerIconStyle getDefaultPointerIconId(); virtual PointerIconStyle getDefaultStylusIconId(); virtual PointerIconStyle getCustomPointerIconId(); @@ -375,7 +380,8 @@ public: /* --- PointerChoreographerPolicyInterface implementation --- */ std::shared_ptr<PointerControllerInterface> createPointerController( PointerControllerInterface::ControllerType type) override; - void notifyPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) override; + void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId, + const FloatPoint& position) override; /* --- InputFilterPolicyInterface implementation --- */ void notifyStickyModifierStateChanged(uint32_t modifierState, @@ -399,7 +405,7 @@ private: int32_t pointerSpeed{0}; // Displays on which its associated mice will have pointer acceleration disabled. - std::set<int32_t> displaysWithMousePointerAccelerationDisabled{}; + std::set<ui::LogicalDisplayId> displaysWithMousePointerAccelerationDisabled{}; // True if pointer gestures are enabled. bool pointerGesturesEnabled{true}; @@ -417,7 +423,7 @@ private: std::set<int32_t> disabledInputDevices{}; // Associated Pointer controller display. - int32_t pointerDisplayId{ADISPLAY_ID_DEFAULT}; + ui::LogicalDisplayId pointerDisplayId{ui::ADISPLAY_ID_DEFAULT}; // True if stylus button reporting through motion events is enabled. bool stylusButtonMotionEventsEnabled{true}; @@ -450,7 +456,7 @@ private: void updateInactivityTimeoutLocked(); void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags); void ensureSpriteControllerLocked(); - sp<SurfaceControl> getParentSurfaceForPointers(int displayId); + sp<SurfaceControl> getParentSurfaceForPointers(ui::LogicalDisplayId displayId); static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName); template <typename T> std::unordered_map<std::string, T> readMapFromInterleavedJavaArray( @@ -459,7 +465,7 @@ private: void forEachPointerControllerLocked(std::function<void(PointerController&)> apply) REQUIRES(mLock); - PointerIcon loadPointerIcon(JNIEnv* env, int32_t displayId, PointerIconStyle type); + PointerIcon loadPointerIcon(JNIEnv* env, ui::LogicalDisplayId displayId, PointerIconStyle type); static inline JNIEnv* jniEnv() { return AndroidRuntime::getJNIEnv(); } }; @@ -490,7 +496,9 @@ void NativeInputManager::dump(std::string& dump) { toString(mLocked.systemUiLightsOut)); dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed); dump += StringPrintf(INDENT "Display with Mouse Pointer Acceleration Disabled: %s\n", - dumpSet(mLocked.displaysWithMousePointerAccelerationDisabled).c_str()); + dumpSet(mLocked.displaysWithMousePointerAccelerationDisabled, + streamableToString) + .c_str()); dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n", toString(mLocked.pointerGesturesEnabled)); dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n", @@ -552,7 +560,7 @@ base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputChann } base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputMonitor( - int32_t displayId, const std::string& name, gui::Pid pid) { + ui::LogicalDisplayId displayId, const std::string& name, gui::Pid pid) { ATRACE_CALL(); return mInputManager->getDispatcher().createInputMonitor(displayId, name, pid); } @@ -616,10 +624,9 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon env->DeleteLocalRef(portAssociations); } - outConfig->uniqueIdAssociationsByPort = - readMapFromInterleavedJavaArray<std::string>(gServiceClassInfo - .getInputUniqueIdAssociations, - "getInputUniqueIdAssociations"); + outConfig->uniqueIdAssociationsByPort = readMapFromInterleavedJavaArray< + std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByPort, + "getInputUniqueIdAssociationsByPort"); outConfig->uniqueIdAssociationsByDescriptor = readMapFromInterleavedJavaArray< std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByDescriptor, @@ -735,7 +742,7 @@ void NativeInputManager::forEachPointerControllerLocked( } } -PointerIcon NativeInputManager::loadPointerIcon(JNIEnv* env, int32_t displayId, +PointerIcon NativeInputManager::loadPointerIcon(JNIEnv* env, ui::LogicalDisplayId displayId, PointerIconStyle type) { if (type == PointerIconStyle::TYPE_CUSTOM) { LOG(FATAL) << __func__ << ": Cannot load non-system icon type"; @@ -766,7 +773,7 @@ std::shared_ptr<PointerControllerInterface> NativeInputManager::createPointerCon return pc; } -void NativeInputManager::notifyPointerDisplayIdChanged(int32_t pointerDisplayId, +void NativeInputManager::notifyPointerDisplayIdChanged(ui::LogicalDisplayId pointerDisplayId, const FloatPoint& position) { // Notify the Reader so that devices can be reconfigured. { // acquire lock @@ -775,7 +782,7 @@ void NativeInputManager::notifyPointerDisplayIdChanged(int32_t pointerDisplayId, return; } mLocked.pointerDisplayId = pointerDisplayId; - ALOGI("%s: pointer displayId set to: %d", __func__, pointerDisplayId); + ALOGI("%s: pointer displayId set to: %s", __func__, pointerDisplayId.toString().c_str()); } // release lock mInputManager->getReader().requestRefreshConfiguration( InputReaderConfiguration::Change::DISPLAY_INFO); @@ -795,7 +802,7 @@ void NativeInputManager::notifyStickyModifierStateChanged(uint32_t modifierState checkAndClearExceptionFromCallback(env, "notifyStickyModifierStateChanged"); } -sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(int displayId) { +sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(ui::LogicalDisplayId displayId) { JNIEnv* env = jniEnv(); jlong nativeSurfaceControlPtr = env->CallLongMethod(mServiceObj, gServiceClassInfo.getParentSurfaceForPointers, @@ -817,9 +824,10 @@ void NativeInputManager::ensureSpriteControllerLocked() REQUIRES(mLock) { layer = -1; } mLocked.spriteController = - std::make_shared<SpriteController>(mLooper, layer, [this](int displayId) { - return getParentSurfaceForPointers(displayId); - }); + std::make_shared<SpriteController>(mLooper, layer, + [this](ui::LogicalDisplayId displayId) { + return getParentSurfaceForPointers(displayId); + }); // The SpriteController needs to be shared pointer because the handler callback needs to hold // a weak reference so that we can avoid racy conditions when the controller is being destroyed. mLocked.spriteController->setHandlerController(mLocked.spriteController); @@ -1021,8 +1029,7 @@ void NativeInputManager::notifyInputChannelBroken(const sp<IBinder>& token) { jobject tokenObj = javaObjectForIBinder(env, token); if (tokenObj) { - env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyInputChannelBroken, - tokenObj); + env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyInputChannelBroken, tokenObj); checkAndClearExceptionFromCallback(env, "notifyInputChannelBroken"); } } @@ -1108,12 +1115,12 @@ void NativeInputManager::notifyVibratorState(int32_t deviceId, bool isOn) { checkAndClearExceptionFromCallback(env, "notifyVibratorState"); } -void NativeInputManager::displayRemoved(JNIEnv* env, int32_t displayId) { +void NativeInputManager::displayRemoved(JNIEnv* env, ui::LogicalDisplayId displayId) { mInputManager->getDispatcher().displayRemoved(displayId); } -void NativeInputManager::setFocusedApplication(JNIEnv* env, int32_t displayId, - jobject applicationHandleObj) { +void NativeInputManager::setFocusedApplication(JNIEnv* env, ui::LogicalDisplayId displayId, + jobject applicationHandleObj) { if (!applicationHandleObj) { return; } @@ -1123,7 +1130,7 @@ void NativeInputManager::setFocusedApplication(JNIEnv* env, int32_t displayId, mInputManager->getDispatcher().setFocusedApplication(displayId, applicationHandle); } -void NativeInputManager::setFocusedDisplay(int32_t displayId) { +void NativeInputManager::setFocusedDisplay(ui::LogicalDisplayId displayId) { mInputManager->getDispatcher().setFocusedDisplay(displayId); } @@ -1151,7 +1158,7 @@ void NativeInputManager::updateInactivityTimeoutLocked() REQUIRES(mLock) { }); } -void NativeInputManager::setPointerDisplayId(int32_t displayId) { +void NativeInputManager::setPointerDisplayId(ui::LogicalDisplayId displayId) { mInputManager->getChoreographer().setDefaultMouseDisplayId(displayId); } @@ -1176,7 +1183,8 @@ void NativeInputManager::setPointerSpeed(int32_t speed) { InputReaderConfiguration::Change::POINTER_SPEED); } -void NativeInputManager::setMousePointerAccelerationEnabled(int32_t displayId, bool enabled) { +void NativeInputManager::setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId, + bool enabled) { { // acquire lock std::scoped_lock _l(mLock); @@ -1186,8 +1194,8 @@ void NativeInputManager::setMousePointerAccelerationEnabled(int32_t displayId, b return; } - ALOGI("Setting mouse pointer acceleration to %s on display %d", toString(enabled), - displayId); + ALOGI("Setting mouse pointer acceleration to %s on display %s", toString(enabled), + displayId.toString().c_str()); if (enabled) { mLocked.displaysWithMousePointerAccelerationDisabled.erase(displayId); } else { @@ -1326,8 +1334,9 @@ void NativeInputManager::reloadPointerIcons() { } bool NativeInputManager::setPointerIcon( - std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, int32_t displayId, - DeviceId deviceId, int32_t pointerId, const sp<IBinder>& inputToken) { + std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, + ui::LogicalDisplayId displayId, DeviceId deviceId, int32_t pointerId, + const sp<IBinder>& inputToken) { if (!mInputManager->getDispatcher().isPointerInWindow(inputToken, displayId, deviceId, pointerId)) { LOG(WARNING) << "Attempted to change the pointer icon for deviceId " << deviceId @@ -1339,7 +1348,7 @@ bool NativeInputManager::setPointerIcon( return mInputManager->getChoreographer().setPointerIcon(std::move(icon), displayId, deviceId); } -void NativeInputManager::setPointerIconVisibility(int32_t displayId, bool visible) { +void NativeInputManager::setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) { mInputManager->getChoreographer().setPointerIconVisibility(displayId, visible); } @@ -1394,7 +1403,7 @@ bool NativeInputManager::isInputMethodConnectionActive() { } std::optional<DisplayViewport> NativeInputManager::getPointerViewportForAssociatedDisplay( - int32_t associatedDisplayId) { + ui::LogicalDisplayId associatedDisplayId) { return mInputManager->getChoreographer().getViewportForPointerDevice(associatedDisplayId); } @@ -1469,9 +1478,9 @@ void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent& keyEvent, handleInterceptActions(wmActions, when, /*byref*/ policyFlags); } -void NativeInputManager::interceptMotionBeforeQueueing(int32_t displayId, uint32_t source, - int32_t action, nsecs_t when, - uint32_t& policyFlags) { +void NativeInputManager::interceptMotionBeforeQueueing(ui::LogicalDisplayId displayId, + uint32_t source, int32_t action, + nsecs_t when, uint32_t& policyFlags) { ATRACE_CALL(); // Policy: // - Ignore untrusted events and pass them along. @@ -1590,7 +1599,8 @@ std::optional<KeyEvent> NativeInputManager::dispatchUnhandledKey(const sp<IBinde return fallbackEvent; } -void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) { +void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType, + ui::LogicalDisplayId displayId) { ATRACE_CALL(); android_server_PowerManagerService_userActivity(eventTime, eventType, displayId); } @@ -1621,13 +1631,14 @@ void NativeInputManager::setPointerCapture(const PointerCaptureRequest& request) InputReaderConfiguration::Change::POINTER_CAPTURE); } -void NativeInputManager::loadPointerIcon(SpriteIcon* icon, int32_t displayId) { +void NativeInputManager::loadPointerIcon(SpriteIcon* icon, ui::LogicalDisplayId displayId) { ATRACE_CALL(); JNIEnv* env = jniEnv(); *icon = toSpriteIcon(loadPointerIcon(env, displayId, PointerIconStyle::TYPE_ARROW)); } -void NativeInputManager::loadPointerResources(PointerResources* outResources, int32_t displayId) { +void NativeInputManager::loadPointerResources(PointerResources* outResources, + ui::LogicalDisplayId displayId) { ATRACE_CALL(); JNIEnv* env = jniEnv(); @@ -1641,7 +1652,8 @@ void NativeInputManager::loadPointerResources(PointerResources* outResources, in void NativeInputManager::loadAdditionalMouseResources( std::map<PointerIconStyle, SpriteIcon>* outResources, - std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, int32_t displayId) { + std::map<PointerIconStyle, PointerAnimation>* outAnimationResources, + ui::LogicalDisplayId displayId) { ATRACE_CALL(); JNIEnv* env = jniEnv(); @@ -1708,7 +1720,7 @@ void NativeInputManager::setStylusButtonMotionEventsEnabled(bool enabled) { InputReaderConfiguration::Change::STYLUS_BUTTON_REPORTING); } -FloatPoint NativeInputManager::getMouseCursorPosition(int32_t displayId) { +FloatPoint NativeInputManager::getMouseCursorPosition(ui::LogicalDisplayId displayId) { return mInputManager->getChoreographer().getMouseCursorPosition(displayId); } @@ -1874,7 +1886,7 @@ static jobject nativeCreateInputMonitor(JNIEnv* env, jobject nativeImplObj, jint jstring nameObj, jint pid) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - if (displayId == ADISPLAY_ID_NONE) { + if (displayId == ui::ADISPLAY_ID_NONE.val()) { std::string message = "InputChannel used as a monitor must be associated with a display"; jniThrowRuntimeException(env, message.c_str()); return nullptr; @@ -1884,7 +1896,7 @@ static jobject nativeCreateInputMonitor(JNIEnv* env, jobject nativeImplObj, jint std::string name = nameChars.c_str(); base::Result<std::unique_ptr<InputChannel>> inputChannel = - im->createInputMonitor(displayId, name, gui::Pid{pid}); + im->createInputMonitor(ui::LogicalDisplayId{displayId}, name, gui::Pid{pid}); if (!inputChannel.ok()) { std::string message = inputChannel.error().message(); @@ -1931,7 +1943,8 @@ static jboolean nativeSetInTouchMode(JNIEnv* env, jobject nativeImplObj, jboolea return im->getInputManager()->getDispatcher().setInTouchMode(inTouchMode, gui::Pid{pid}, gui::Uid{static_cast<uid_t>(uid)}, - hasPermission, displayId); + hasPermission, + ui::LogicalDisplayId{displayId}); } static void nativeSetMaximumObscuringOpacityForTouch(JNIEnv* env, jobject nativeImplObj, @@ -2023,20 +2036,20 @@ static void nativeToggleCapsLock(JNIEnv* env, jobject nativeImplObj, jint device static void nativeDisplayRemoved(JNIEnv* env, jobject nativeImplObj, jint displayId) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->displayRemoved(env, displayId); + im->displayRemoved(env, ui::LogicalDisplayId{displayId}); } static void nativeSetFocusedApplication(JNIEnv* env, jobject nativeImplObj, jint displayId, jobject applicationHandleObj) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->setFocusedApplication(env, displayId, applicationHandleObj); + im->setFocusedApplication(env, ui::LogicalDisplayId{displayId}, applicationHandleObj); } static void nativeSetFocusedDisplay(JNIEnv* env, jobject nativeImplObj, jint displayId) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->setFocusedDisplay(displayId); + im->setFocusedDisplay(ui::LogicalDisplayId{displayId}); } static void nativeSetUserActivityPokeInterval(JNIEnv* env, jobject nativeImplObj, @@ -2092,8 +2105,8 @@ static jboolean nativeTransferTouchOnDisplay(JNIEnv* env, jobject nativeImplObj, NativeInputManager* im = getNativeInputManager(env, nativeImplObj); if (im->getInputManager()->getDispatcher().transferTouchOnDisplay(destChannelToken, - static_cast<int32_t>( - displayId))) { + ui::LogicalDisplayId{ + displayId})) { return JNI_TRUE; } else { return JNI_FALSE; @@ -2116,7 +2129,7 @@ static void nativeSetMousePointerAccelerationEnabled(JNIEnv* env, jobject native jint displayId, jboolean enabled) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->setMousePointerAccelerationEnabled(displayId, enabled); + im->setMousePointerAccelerationEnabled(ui::LogicalDisplayId{displayId}, enabled); } static void nativeSetTouchpadPointerSpeed(JNIEnv* env, jobject nativeImplObj, jint speed) { @@ -2486,7 +2499,7 @@ static bool nativeSetPointerIcon(JNIEnv* env, jobject nativeImplObj, jobject ico icon = pointerIcon.style; } - return im->setPointerIcon(std::move(icon), displayId, deviceId, pointerId, + return im->setPointerIcon(std::move(icon), ui::LogicalDisplayId{displayId}, deviceId, pointerId, ibinderForJavaObject(env, inputTokenObj)); } @@ -2494,13 +2507,14 @@ static void nativeSetPointerIconVisibility(JNIEnv* env, jobject nativeImplObj, j jboolean visible) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->setPointerIconVisibility(displayId, visible); + im->setPointerIconVisibility(ui::LogicalDisplayId{displayId}, visible); } static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint displayId) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - return im->getInputManager()->getReader().canDispatchToDisplay(deviceId, displayId); + return im->getInputManager()->getReader().canDispatchToDisplay(deviceId, + ui::LogicalDisplayId{displayId}); } static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jobject nativeImplObj) { @@ -2512,8 +2526,9 @@ static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jobject nativeImplO static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jobject nativeImplObj, jint displayId, jboolean isEligible) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->getInputManager()->getDispatcher().setDisplayEligibilityForPointerCapture(displayId, - isEligible); + im->getInputManager() + ->getDispatcher() + .setDisplayEligibilityForPointerCapture(ui::LogicalDisplayId{displayId}, isEligible); } static void nativeChangeUniqueIdAssociation(JNIEnv* env, jobject nativeImplObj) { @@ -2649,7 +2664,7 @@ static void nativeCancelCurrentTouch(JNIEnv* env, jobject nativeImplObj) { static void nativeSetPointerDisplayId(JNIEnv* env, jobject nativeImplObj, jint displayId) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->setPointerDisplayId(displayId); + im->setPointerDisplayId(ui::LogicalDisplayId{displayId}); } static jstring nativeGetBluetoothAddress(JNIEnv* env, jobject nativeImplObj, jint deviceId) { @@ -2667,7 +2682,7 @@ static void nativeSetStylusButtonMotionEventsEnabled(JNIEnv* env, jobject native static jfloatArray nativeGetMouseCursorPosition(JNIEnv* env, jobject nativeImplObj, jint displayId) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - const auto p = im->getMouseCursorPosition(displayId); + const auto p = im->getMouseCursorPosition(ui::LogicalDisplayId{displayId}); const std::array<float, 2> arr = {{p.x, p.y}}; jfloatArray outArr = env->NewFloatArray(2); env->SetFloatArrayRegion(outArr, 0, arr.size(), arr.data()); @@ -2930,8 +2945,8 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gServiceClassInfo.getInputPortAssociations, clazz, "getInputPortAssociations", "()[Ljava/lang/String;"); - GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociations, clazz, - "getInputUniqueIdAssociations", "()[Ljava/lang/String;"); + GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociationsByPort, clazz, + "getInputUniqueIdAssociationsByPort", "()[Ljava/lang/String;"); GET_METHOD_ID(gServiceClassInfo.getInputUniqueIdAssociationsByDescriptor, clazz, "getInputUniqueIdAssociationsByDescriptor", "()[Ljava/lang/String;"); diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp index d0b290c05ee9..073396848c55 100644 --- a/services/core/jni/com_android_server_power_PowerManagerService.cpp +++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp @@ -99,7 +99,7 @@ static bool setPowerMode(Mode mode, bool enabled) { } void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType, - int32_t displayId) { + ui::LogicalDisplayId displayId) { if (gPowerManagerServiceObj) { // Throttle calls into user activity by event type. // We're a little conservative about argument checking here in case the caller @@ -124,8 +124,8 @@ void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t JNIEnv* env = AndroidRuntime::getJNIEnv(); env->CallVoidMethod(gPowerManagerServiceObj, - gPowerManagerServiceClassInfo.userActivityFromNative, - nanoseconds_to_milliseconds(eventTime), eventType, displayId, 0); + gPowerManagerServiceClassInfo.userActivityFromNative, + nanoseconds_to_milliseconds(eventTime), eventType, displayId.val(), 0); checkAndClearExceptionFromCallback(env, "userActivityFromNative"); } } diff --git a/services/core/jni/com_android_server_power_PowerManagerService.h b/services/core/jni/com_android_server_power_PowerManagerService.h index 36aaceb029c7..ed7fa7c39bd3 100644 --- a/services/core/jni/com_android_server_power_PowerManagerService.h +++ b/services/core/jni/com_android_server_power_PowerManagerService.h @@ -19,6 +19,7 @@ #include <nativehelper/JNIHelp.h> #include <powermanager/PowerManager.h> +#include <ui/LogicalDisplayId.h> #include <utils/Timers.h> #include "jni.h" @@ -26,7 +27,7 @@ namespace android { extern void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType, - int32_t displayId); + ui::LogicalDisplayId displayId); } // namespace android diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index 0c83e8e468d9..2e126f1b50ba 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -295,7 +295,7 @@ public final class CredentialManagerService } } - private static Set<ComponentName> getPrimaryProvidersForUserId(Context context, int userId) { + static Set<ComponentName> getPrimaryProvidersForUserId(Context context, int userId) { final int resolvedUserId = ActivityManager.handleIncomingUser( Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java index 38ad5b6594b5..b86db065cfd7 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java @@ -16,6 +16,8 @@ package com.android.server.credentials; +import static com.android.server.credentials.CredentialManagerService.getPrimaryProvidersForUserId; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; @@ -30,6 +32,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.server.infra.AbstractPerUserSystemService; import java.util.List; +import java.util.Set; /** @@ -80,9 +83,12 @@ public final class CredentialManagerServiceImpl extends Slog.i(TAG, "newServiceInfoLocked, mInfo null, " + serviceComponent.flattenToString()); } + Set<ComponentName> primaryProviders = + getPrimaryProvidersForUserId(mMaster.getContext(), mUserId); mInfo = CredentialProviderInfoFactory.create( getContext(), serviceComponent, - mUserId, /*isSystemProvider=*/false); + mUserId, /*isSystemProvider=*/false, + primaryProviders.contains(serviceComponent)); return mInfo.getServiceInfo(); } diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java index b1673e2c4c3c..7a026d5bd301 100644 --- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java @@ -67,6 +67,9 @@ public class GetCandidateRequestSession extends RequestSession<GetCredentialRequ private final ResultReceiver mAutofillCallback; + @Nullable + private ComponentName mPrimaryProviderComponentName = null; + public GetCandidateRequestSession( Context context, SessionLifetime sessionCallback, Object lock, int userId, int callingUid, @@ -104,8 +107,12 @@ public class GetCandidateRequestSession extends RequestSession<GetCredentialRequ if (providerGetCandidateSessions != null) { Slog.d(TAG, "In startProviderSession - provider session created and " + "being added for: " + providerInfo.getComponentName()); - mProviders.put(providerGetCandidateSessions.getComponentName().flattenToString(), - providerGetCandidateSessions); + ComponentName componentName = providerGetCandidateSessions + .getComponentName(); + if (providerInfo.isPrimary()) { + mPrimaryProviderComponentName = componentName; + } + mProviders.put(componentName.flattenToString(), providerGetCandidateSessions); } return providerGetCandidateSessions; } @@ -138,7 +145,7 @@ public class GetCandidateRequestSession extends RequestSession<GetCredentialRequ try { invokeClientCallbackSuccess(new GetCandidateCredentialsResponse( - candidateProviderDataList, intent)); + candidateProviderDataList, intent, mPrimaryProviderComponentName)); } catch (RemoteException e) { Slog.e(TAG, "Issue while responding to client with error : " + e); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 375fc5a0280a..2b93d2132a8f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -339,6 +339,7 @@ import android.app.admin.ManagedProfileProvisioningParams; import android.app.admin.ManagedSubscriptionsPolicy; import android.app.admin.NetworkEvent; import android.app.admin.PackagePolicy; +import android.app.admin.PackageSetPolicyValue; import android.app.admin.ParcelableGranteeMap; import android.app.admin.ParcelableResource; import android.app.admin.PasswordMetrics; @@ -349,7 +350,6 @@ import android.app.admin.PolicyValue; import android.app.admin.PreferentialNetworkServiceConfig; import android.app.admin.SecurityLog; import android.app.admin.SecurityLog.SecurityEvent; -import android.app.admin.PackageSetPolicyValue; import android.app.admin.StartInstallingUpdateCallback; import android.app.admin.SystemUpdateInfo; import android.app.admin.SystemUpdatePolicy; @@ -2718,6 +2718,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mDevicePolicyEngine.getResolvedPolicy( PolicyDefinition.SECURITY_LOGGING, UserHandle.USER_ALL)); setLoggingConfiguration(securityLoggingEnabled, auditLoggingEnabled); + mInjector.runCryptoSelfTest(); } else { synchronized (getLockObject()) { mSecurityLogMonitor.start(getSecurityLoggingEnabledUser()); @@ -15172,10 +15173,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (statusBarService != null) { int flags1 = disabled ? STATUS_BAR_DISABLE_MASK : StatusBarManager.DISABLE_NONE; int flags2 = disabled ? STATUS_BAR_DISABLE2_MASK : StatusBarManager.DISABLE2_NONE; - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(flags1, - flags2); - statusBarService.disableForUser(info, mToken, mContext.getPackageName(), userId, - "setStatusBarDisabledInternal"); + statusBarService.disableForUser(flags1, mToken, mContext.getPackageName(), userId); + statusBarService.disable2ForUser(flags2, mToken, mContext.getPackageName(), userId); return true; } } catch (RemoteException e) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java index 4bf3ff4265d4..09eef451c547 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java @@ -196,19 +196,27 @@ final class PolicyEnforcerCallbacks { Binder.withCleanCallingIdentity(() -> { PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); + AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); + pmi.setOwnerProtectedPackages(userId, packages == null ? null : packages.stream().toList()); LocalServices.getService(UsageStatsManagerInternal.class) .setAdminProtectedPackages( packages == null ? null : new ArraySet<>(packages), userId); - if (Flags.disallowUserControlBgUsageFix()) { - if (packages == null) { - return; + if (packages == null || packages.isEmpty()) { + return; + } + + for (int user : resolveUsers(userId)) { + if (Flags.disallowUserControlBgUsageFix()) { + setBgUsageAppOp(packages, pmi, user, appOpsManager); + } + if (Flags.disallowUserControlStoppedStateFix()) { + for (String packageName : packages) { + pmi.setPackageStoppedState(packageName, false, user); + } } - final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); - resolveUsers(userId).forEach( - user -> setBgUsageAppOp(packages, pmi, user, appOpsManager)); } }); return true; diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt index 9e4f8219ea96..d3072000a56e 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt @@ -1276,7 +1276,23 @@ class AppIdPermissionPolicy : SchemePolicy() { packageName, permissionName ) - else -> permissionAllowlist.getSignatureAppAllowlistState(packageName, permissionName) + else -> + permissionAllowlist.getProductSignatureAppAllowlistState( + packageName, + permissionName + ) + ?: permissionAllowlist.getVendorSignatureAppAllowlistState( + packageName, + permissionName + ) + ?: permissionAllowlist.getSystemExtSignatureAppAllowlistState( + packageName, + permissionName + ) + ?: permissionAllowlist.getSignatureAppAllowlistState( + packageName, + permissionName + ) } } diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index b155829208ef..649955614851 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -2046,8 +2046,20 @@ class PermissionService(private val service: AccessCheckingService) : writer.println("Unknown app ID $appId.") } } + } else if (args[0] == "--package" && args.size == 2) { + val packageName = args[1] + service.getState { + val packageState = state.externalState.packageStates[packageName] + if (packageState != null) { + writer.dumpAppIdState(packageState.appId, state, indexedSetOf(packageName)) + } else { + writer.println("Unknown package $packageName.") + } + } } else { - writer.println("Usage: dumpsys permission [--app-id APP_ID]") + writer.println( + "Usage: dumpsys permissionmgr [--app-id <APP_ID>] [--package <PACKAGE_NAME>]" + ) } } diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java index 622e70279700..54d101a3c1cf 100644 --- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java +++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java @@ -243,12 +243,12 @@ public final class ProfcollectForwardingService extends SystemService { return; } sSelfService.mIProfcollect.process(); - jobFinished(params, false); } catch (RemoteException e) { Log.e(LOG_TAG, "Failed to process profiles in background: " + e.getMessage()); } }); + jobFinished(params, false); return true; } diff --git a/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java b/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java index 6a82f1656414..3e87c6fe7be7 100644 --- a/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java +++ b/services/robotests/backup/src/com/android/server/backup/transport/TransportConnectionTest.java @@ -28,6 +28,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -543,6 +544,18 @@ public class TransportConnectionTest { return future.get(); } + @Test + public void onBindingDied_referenceLost_doesNotThrow() { + TransportConnection.TransportConnectionMonitor transportConnectionMonitor = + new TransportConnection.TransportConnectionMonitor( + mContext, /* transportConnection= */ null); + doThrow(new IllegalArgumentException("Service not registered")).when( + mContext).unbindService(any()); + + // Test no exception is thrown + transportConnectionMonitor.onBindingDied(mTransportComponent); + } + private ServiceConnection verifyBindServiceAsUserAndCaptureServiceConnection(Context context) { ArgumentCaptor<ServiceConnection> connectionCaptor = ArgumentCaptor.forClass(ServiceConnection.class); diff --git a/services/tests/apexsystemservices/OWNERS b/services/tests/apexsystemservices/OWNERS index 0295b9e99326..8b6675ad22d7 100644 --- a/services/tests/apexsystemservices/OWNERS +++ b/services/tests/apexsystemservices/OWNERS @@ -1,4 +1 @@ -omakoto@google.com -satayev@google.com - include platform/packages/modules/common:/OWNERS 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 1666fef13685..54b2d4dd1429 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -282,7 +282,7 @@ public class DisplayManagerServiceTest { return new VirtualDisplayAdapter(syncRoot, context, handler, displayAdapterListener, new VirtualDisplayAdapter.SurfaceControlDisplayFactory() { @Override - public IBinder createDisplay(String name, boolean secure, + public IBinder createDisplay(String name, boolean secure, String uniqueId, float requestedRefreshRate) { return mMockDisplayToken; } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index 80f38eb52d97..e5685c7f4f43 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -1790,6 +1790,7 @@ public final class DisplayPowerControllerTest { verify(mHolder.animator).animateTo(eq(brightness), /* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(), /* ignoreAnimationLimits= */ anyBoolean()); + verify(mHolder.brightnessSetting).setBrightness(brightness); } @Test diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index 4a2164582890..42814e7c775e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -239,6 +239,9 @@ public class RescuePartyTest { @Test public void testBootLoopDetectionWithExecutionForAllRescueLevels() { + // this is old test where the flag needs to be disabled + mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + RescueParty.onSettingsProviderPublished(mMockContext); verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver), any(Executor.class), @@ -449,6 +452,9 @@ public class RescuePartyTest { @Test public void testNonPersistentAppCrashDetectionWithScopedResets() { + // this is old test where the flag needs to be disabled + mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + RescueParty.onSettingsProviderPublished(mMockContext); verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver), any(Executor.class), @@ -506,6 +512,9 @@ public class RescuePartyTest { @Test public void testNonDeviceConfigSettingsOnlyResetOncePerLevel() { + // this is old test where the flag needs to be disabled + mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + RescueParty.onSettingsProviderPublished(mMockContext); verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver), any(Executor.class), @@ -879,6 +888,9 @@ public class RescuePartyTest { @Test public void testBootLoopLevels() { + // this is old test where the flag needs to be disabled + mSetFlagsRule.disableFlags(Flags.FLAG_RECOVERABILITY_DETECTION); + RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); assertEquals(observer.onBootLoop(0), PackageHealthObserverImpact.USER_IMPACT_LEVEL_0); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index cc69c1dbf999..28c7fb2396dd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -956,6 +956,7 @@ public class MockingOomAdjusterTests { ConnectionRecord cr = s.getConnections().get(binder).get(0); setFieldValue(ConnectionRecord.class, cr, "activity", mock(ActivityServiceConnectionsHolder.class)); + doReturn(client).when(sService).getTopApp(); doReturn(true).when(cr.activity).isActivityVisible(); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); updateOomAdj(client, app); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java index 599b9cd06ee1..8e1e3392eb1c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java @@ -273,10 +273,8 @@ public class SettingsToPropertiesMapperTest { keyValueMap.put("namespace_1*flag_1", "true"); // case 2: existing prop, stage a different value keyValueMap.put("namespace_1*flag_2", "false"); - // case 3: new prop, stage the non default value + // case 3: new prop keyValueMap.put("namespace_2*flag_1", "true"); - // case 4: new prop, stage the default value - keyValueMap.put("namespace_2*flag_2", "false"); Properties props = new Properties(namespace, keyValueMap); HashMap<String, HashMap<String, String>> toStageProps = @@ -290,11 +288,9 @@ public class SettingsToPropertiesMapperTest { String namespace_1_flag_1 = namespace_1_to_stage.get("flag_1"); String namespace_1_flag_2 = namespace_1_to_stage.get("flag_2"); String namespace_2_flag_1 = namespace_2_to_stage.get("flag_1"); - String namespace_2_flag_2 = namespace_2_to_stage.get("flag_2"); Assert.assertTrue(namespace_1_flag_1 == null); Assert.assertTrue(namespace_1_flag_2 != null); Assert.assertTrue(namespace_2_flag_1 != null); - Assert.assertTrue(namespace_2_flag_2 == null); Assert.assertTrue(namespace_1_flag_2.equals("false")); Assert.assertTrue(namespace_2_flag_1.equals("true")); } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java index 7d58a2e53693..79f1574105ba 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -778,49 +778,6 @@ public final class UserManagerServiceTest { } @Test - public void testGetAliveUsers_shouldExcludeInitialisedEphemeralNonCurrentUsers() { - assertWithMessage("Ephemeral user should not exist at all initially") - .that(mUmi.getUsers(false).stream().anyMatch(u -> u.id == USER_ID)) - .isFalse(); - - // add an ephemeral full user - TestUserData userData = new TestUserData(USER_ID); - userData.info.flags = UserInfo.FLAG_FULL | UserInfo.FLAG_EPHEMERAL; - addUserData(userData); - - assertWithMessage("Ephemeral user should exist as alive after being created") - .that(mUmi.getUsers(true).stream().anyMatch(u -> u.id == USER_ID)) - .isTrue(); - - // mock switch to the user (mark it as initialized & make it the current user) - userData.info.flags |= UserInfo.FLAG_INITIALIZED; - mockCurrentUser(USER_ID); - - assertWithMessage("Ephemeral user should still exist as alive after being switched to") - .that(mUmi.getUsers(true).stream().anyMatch(u -> u.id == USER_ID)) - .isTrue(); - - // switch away from the user - mockCurrentUser(OTHER_USER_ID); - - assertWithMessage("Ephemeral user should not exist as alive after getting switched away") - .that(mUmi.getUsers(true).stream().anyMatch(u -> u.id == USER_ID)) - .isFalse(); - - assertWithMessage("Ephemeral user should still exist as dying after getting switched away") - .that(mUmi.getUsers(false).stream().anyMatch(u -> u.id == USER_ID)) - .isTrue(); - - // finally remove the user - mUms.removeUserInfo(USER_ID); - - assertWithMessage("Ephemeral user should not exist at all after cleanup") - .that(mUmi.getUsers(false).stream().anyMatch(u -> u.id == USER_ID)) - .isFalse(); - } - - - @Test @RequiresFlagsEnabled({android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION, Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES}) public void testCreatePrivateProfileOnHeadlessSystemUser_shouldAllowCreation() { diff --git a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java index 88ca02933450..ec78bcea7539 100644 --- a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java @@ -112,7 +112,7 @@ public class PinnerServiceTest { resources.addOverride( com.android.internal.R.array.config_defaultPinnerServiceFiles, new String[0]); resources.addOverride(com.android.internal.R.bool.config_pinnerCameraApp, false); - resources.addOverride(com.android.internal.R.bool.config_pinnerHomeApp, false); + resources.addOverride(com.android.internal.R.integer.config_pinnerHomePinBytes, 0); resources.addOverride(com.android.internal.R.bool.config_pinnerAssistantApp, false); mFakeDeviceConfigInterface = new FakeDeviceConfigInterface(); @@ -242,7 +242,7 @@ public class PinnerServiceTest { public void testPinHomeApp() throws Exception { // Enable HOME app pinning mContext.getOrCreateTestableResources() - .addOverride(com.android.internal.R.bool.config_pinnerHomeApp, true); + .addOverride(com.android.internal.R.integer.config_pinnerHomePinBytes, 1024); PinnerService pinnerService = new PinnerService(mContext, mInjector); pinnerService.onStart(); @@ -266,7 +266,7 @@ public class PinnerServiceTest { public void testPinHomeAppOnBootCompleted() throws Exception { // Enable HOME app pinning mContext.getOrCreateTestableResources() - .addOverride(com.android.internal.R.bool.config_pinnerHomeApp, true); + .addOverride(com.android.internal.R.integer.config_pinnerHomePinBytes, 1024); PinnerService pinnerService = new PinnerService(mContext, mInjector); pinnerService.onStart(); diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java new file mode 100644 index 000000000000..7f2327aa4f24 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsDeviceAwareServiceTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appop; + +import static android.app.AppOpsManager.OP_CAMERA; +import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED; +import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM; +import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA; + +import static com.google.common.truth.Truth.assertThat; + +import android.Manifest; +import android.app.AppOpsManager; +import android.companion.virtual.VirtualDeviceManager; +import android.companion.virtual.VirtualDeviceParams; +import android.content.AttributionSource; +import android.os.Process; +import android.permission.PermissionManager; +import android.permission.flags.Flags; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.testing.TestableContext; +import android.virtualdevice.cts.common.VirtualDeviceRule; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; +import java.util.Objects; + +@RunWith(AndroidJUnit4.class) +public class AppOpsDeviceAwareServiceTest { + + @Rule + public final TestableContext mContext = + new TestableContext(InstrumentationRegistry.getInstrumentation().getTargetContext()); + + @Rule + public VirtualDeviceRule virtualDeviceRule = + VirtualDeviceRule.withAdditionalPermissions( + Manifest.permission.GRANT_RUNTIME_PERMISSIONS, + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, + Manifest.permission.CREATE_VIRTUAL_DEVICE, + Manifest.permission.GET_APP_OPS_STATS); + + private static final String ATTRIBUTION_TAG_1 = "attributionTag1"; + private static final String ATTRIBUTION_TAG_2 = "attributionTag2"; + private final AppOpsManager mAppOpsManager = mContext.getSystemService(AppOpsManager.class); + private final PermissionManager mPermissionManager = + mContext.getSystemService(PermissionManager.class); + + private VirtualDeviceManager.VirtualDevice mVirtualDevice; + + @Before + public void setUp() { + mVirtualDevice = + virtualDeviceRule.createManagedVirtualDevice( + new VirtualDeviceParams.Builder() + .setDevicePolicy(POLICY_TYPE_CAMERA, DEVICE_POLICY_CUSTOM) + .build()); + + mPermissionManager.grantRuntimePermission( + mContext.getOpPackageName(), + Manifest.permission.CAMERA, + mVirtualDevice.getPersistentDeviceId()); + + mPermissionManager.grantRuntimePermission( + mContext.getOpPackageName(), + Manifest.permission.CAMERA, + VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); + } + + @RequiresFlagsEnabled(Flags.FLAG_DEVICE_ID_IN_OP_PROXY_INFO_ENABLED) + @Test + public void noteProxyOp_proxyAppOnDefaultDevice() { + AppOpsManager.OpEventProxyInfo proxyInfo = + noteProxyOpWithDeviceId(TestableContext.DEVICE_ID_DEFAULT); + assertThat(proxyInfo.getDeviceId()) + .isEqualTo(VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT); + } + + @RequiresFlagsEnabled(Flags.FLAG_DEVICE_ID_IN_OP_PROXY_INFO_ENABLED) + @Test + public void noteProxyOp_proxyAppOnRemoteDevice() { + AppOpsManager.OpEventProxyInfo proxyInfo = + noteProxyOpWithDeviceId(mVirtualDevice.getDeviceId()); + assertThat(proxyInfo.getDeviceId()).isEqualTo(mVirtualDevice.getPersistentDeviceId()); + } + + private AppOpsManager.OpEventProxyInfo noteProxyOpWithDeviceId(int proxyAppDeviceId) { + AttributionSource proxiedAttributionSource = + new AttributionSource.Builder(Process.myUid()) + .setPackageName(mContext.getOpPackageName()) + .setAttributionTag(ATTRIBUTION_TAG_2) + .setDeviceId(mVirtualDevice.getDeviceId()) + .build(); + + AttributionSource proxyAttributionSource = + new AttributionSource.Builder(Process.myUid()) + .setPackageName(mContext.getOpPackageName()) + .setAttributionTag(ATTRIBUTION_TAG_1) + .setDeviceId(proxyAppDeviceId) + .setNextAttributionSource(proxiedAttributionSource) + .build(); + + int mode = mAppOpsManager.noteProxyOp(OP_CAMERA, proxyAttributionSource, null, false); + assertThat(mode).isEqualTo(AppOpsManager.MODE_ALLOWED); + + List<AppOpsManager.PackageOps> packagesOps = + mAppOpsManager.getPackagesForOps( + new String[] {AppOpsManager.OPSTR_CAMERA}, + mVirtualDevice.getPersistentDeviceId()); + + AppOpsManager.PackageOps packageOps = + packagesOps.stream() + .filter( + pkg -> + Objects.equals( + pkg.getPackageName(), mContext.getOpPackageName())) + .findFirst() + .orElseThrow(); + + AppOpsManager.OpEntry opEntry = + packageOps.getOps().stream() + .filter(op -> op.getOp() == OP_CAMERA) + .findFirst() + .orElseThrow(); + + return opEntry.getLastProxyInfo(OP_FLAGS_ALL_TRUSTED); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java index 3dc375c5436d..9cd3186f99f3 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java @@ -368,24 +368,32 @@ public class AuthServiceTest { } @Test - public void testAuthenticate_throwsWhenUsingTestConfigurations() { + public void testAuthenticate_throwsWhenUsingTestApis() { final PromptInfo promptInfo = mock(PromptInfo.class); - when(promptInfo.containsPrivateApiConfigurations()).thenReturn(false); - when(promptInfo.containsTestConfigurations()).thenReturn(true); + when(promptInfo.requiresInternalPermission()).thenReturn(false); + when(promptInfo.requiresTestOrInternalPermission()).thenReturn(true); - testAuthenticate_throwsWhenUsingTestConfigurations(promptInfo); + testAuthenticate_throwsSecurityException(promptInfo); } @Test public void testAuthenticate_throwsWhenUsingPrivateApis() { final PromptInfo promptInfo = mock(PromptInfo.class); - when(promptInfo.containsPrivateApiConfigurations()).thenReturn(true); - when(promptInfo.containsTestConfigurations()).thenReturn(false); + when(promptInfo.requiresInternalPermission()).thenReturn(true); + when(promptInfo.requiresTestOrInternalPermission()).thenReturn(false); - testAuthenticate_throwsWhenUsingTestConfigurations(promptInfo); + testAuthenticate_throwsSecurityException(promptInfo); } - private void testAuthenticate_throwsWhenUsingTestConfigurations(PromptInfo promptInfo) { + @Test + public void testAuthenticate_throwsWhenUsingAdvancedApis() { + final PromptInfo promptInfo = mock(PromptInfo.class); + when(promptInfo.requiresAdvancedPermission()).thenReturn(true); + + testAuthenticate_throwsSecurityException(promptInfo); + } + + private void testAuthenticate_throwsSecurityException(PromptInfo promptInfo) { mAuthService = new AuthService(mContext, mInjector); mAuthService.onStart(); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java index 0716a5c5561d..3698d6fb6b76 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.NotificationManager; +import android.content.Context; import android.content.Intent; import android.hardware.biometrics.BiometricsProtoEnums; import android.os.UserHandle; @@ -75,13 +76,15 @@ public class BiometricDanglingReceiverTest { @Test public void testFingerprintRegisterReceiver() { initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FINGERPRINT); - verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any()); + verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any(), + eq(Context.RECEIVER_NOT_EXPORTED)); } @Test public void testFaceRegisterReceiver() { initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FACE); - verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any()); + verify(mContext).registerReceiver(eq(mBiometricDanglingReceiver), any(), + eq(Context.RECEIVER_NOT_EXPORTED)); } @Test diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java index b33a8aa28544..00c8ed188d1e 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java @@ -53,7 +53,7 @@ class InputManagerMockHelper { private IInputDevicesChangedListener mDevicesChangedListener; private final Map<String /* uniqueId */, Integer /* displayId */> mDisplayIdMapping = new HashMap<>(); - private final Map<String /* phys */, String /* uniqueId */> mUniqueIdAssociation = + private final Map<String /* phys */, String /* uniqueId */> mUniqueIdAssociationByPort = new HashMap<>(); InputManagerMockHelper(TestableLooper testableLooper, @@ -79,11 +79,11 @@ class InputManagerMockHelper { when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]); doAnswer(inv -> mDevices.get(inv.getArgument(0))) .when(mIInputManagerMock).getInputDevice(anyInt()); - doAnswer(inv -> mUniqueIdAssociation.put(inv.getArgument(0), - inv.getArgument(1))).when(mIInputManagerMock).addUniqueIdAssociation( + doAnswer(inv -> mUniqueIdAssociationByPort.put(inv.getArgument(0), + inv.getArgument(1))).when(mIInputManagerMock).addUniqueIdAssociationByPort( anyString(), anyString()); - doAnswer(inv -> mUniqueIdAssociation.remove(inv.getArgument(0))).when( - mIInputManagerMock).removeUniqueIdAssociation(anyString()); + doAnswer(inv -> mUniqueIdAssociationByPort.remove(inv.getArgument(0))).when( + mIInputManagerMock).removeUniqueIdAssociationByPort(anyString()); // Set a new instance of InputManager for testing that uses the IInputManager mock as the // interface to the server. @@ -113,7 +113,7 @@ class InputManagerMockHelper { .setDescriptor(phys) .setExternal(true) .setAssociatedDisplayId( - mDisplayIdMapping.getOrDefault(mUniqueIdAssociation.get(phys), + mDisplayIdMapping.getOrDefault(mUniqueIdAssociationByPort.get(phys), Display.INVALID_DISPLAY)) .build(); diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java index 40ecaf1770a9..7dd1847114c8 100644 --- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java +++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java @@ -42,6 +42,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Binder; +import android.os.Bundle; import android.os.HandlerThread; import android.os.LocaleList; import android.os.Process; @@ -488,7 +489,7 @@ public class LocaleManagerBackupRestoreTest { setUpPackageInstalled(pkgNameA); - mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID); + mPackageMonitor.onPackageAddedWithExtras(pkgNameA, DEFAULT_UID, new Bundle()); verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID, LocaleList.forLanguageTags(langTagsA), false, FrameworkStatsLog @@ -504,7 +505,7 @@ public class LocaleManagerBackupRestoreTest { setUpPackageInstalled(pkgNameB); - mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID); + mPackageMonitor.onPackageAddedWithExtras(pkgNameB, DEFAULT_UID, new Bundle()); verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID, LocaleList.forLanguageTags(langTagsB), true, FrameworkStatsLog @@ -518,6 +519,66 @@ public class LocaleManagerBackupRestoreTest { } @Test + public void testRestore_appInstalledAfterSUW_restoresFromStage_ArchiveEnabled() + throws Exception { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + HashMap<String, LocalesInfo> pkgLocalesMap = new HashMap<>(); + String pkgNameA = "com.android.myAppA"; + String pkgNameB = "com.android.myAppB"; + String langTagsA = "ru"; + String langTagsB = "hi,fr"; + LocalesInfo localesInfoA = new LocalesInfo(langTagsA, false); + LocalesInfo localesInfoB = new LocalesInfo(langTagsB, true); + pkgLocalesMap.put(pkgNameA, localesInfoA); + pkgLocalesMap.put(pkgNameB, localesInfoB); + writeTestPayload(out, pkgLocalesMap); + setUpPackageNotInstalled(pkgNameA); + setUpPackageNotInstalled(pkgNameB); + setUpLocalesForPackage(pkgNameA, LocaleList.getEmptyLocaleList()); + setUpLocalesForPackage(pkgNameB, LocaleList.getEmptyLocaleList()); + setUpPackageNamesForSp(new ArraySet<>()); + + Bundle bundle = new Bundle(); + bundle.putBoolean(Intent.EXTRA_ARCHIVAL, true); + mPackageMonitor.onPackageAddedWithExtras(pkgNameA, DEFAULT_UID, bundle); + mPackageMonitor.onPackageAddedWithExtras(pkgNameB, DEFAULT_UID, bundle); + + mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID); + + verifyNothingRestored(); + + setUpPackageInstalled(pkgNameA); + + mPackageMonitor.onPackageUpdateFinished(pkgNameA, DEFAULT_UID); + + verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID, + LocaleList.forLanguageTags(langTagsA), false, FrameworkStatsLog + .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE); + + mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameA, false, false); + + verify(mMockSpEditor, times(0)).putStringSet(anyString(), any()); + + pkgLocalesMap.remove(pkgNameA); + + verifyStageDataForUser(pkgLocalesMap, DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID); + + setUpPackageInstalled(pkgNameB); + + mPackageMonitor.onPackageUpdateFinished(pkgNameB, DEFAULT_UID); + + verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID, + LocaleList.forLanguageTags(langTagsB), true, FrameworkStatsLog + .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE); + + mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameB, true, false); + + verify(mMockSpEditor, times(1)).putStringSet(Integer.toString(DEFAULT_USER_ID), + new ArraySet<>(Arrays.asList(pkgNameB))); + checkStageDataDoesNotExist(DEFAULT_USER_ID); + } + + @Test public void testRestore_appInstalledAfterSUWAndLocalesAlreadySet_restoresNothing() throws Exception { final ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -535,7 +596,7 @@ public class LocaleManagerBackupRestoreTest { setUpPackageInstalled(DEFAULT_PACKAGE_NAME); setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.forLanguageTags("hi,mr")); - mPackageMonitor.onPackageAdded(DEFAULT_PACKAGE_NAME, DEFAULT_UID); + mPackageMonitor.onPackageAddedWithExtras(DEFAULT_PACKAGE_NAME, DEFAULT_UID, new Bundle()); // Since locales are already set, we should not restore anything for it. verifyNothingRestored(); @@ -612,7 +673,7 @@ public class LocaleManagerBackupRestoreTest { DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.minusHours(1).toMillis()); setUpPackageInstalled(pkgNameA); - mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID); + mPackageMonitor.onPackageAddedWithExtras(pkgNameA, DEFAULT_UID, new Bundle()); verify(mMockLocaleManagerService, times(1)).setApplicationLocales( pkgNameA, DEFAULT_USER_ID, LocaleList.forLanguageTags(langTagsA), false, @@ -627,7 +688,7 @@ public class LocaleManagerBackupRestoreTest { DEFAULT_CREATION_TIME_MILLIS + RETENTION_PERIOD.plusSeconds(1).toMillis()); setUpPackageInstalled(pkgNameB); - mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID); + mPackageMonitor.onPackageAddedWithExtras(pkgNameB, DEFAULT_UID, new Bundle()); verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameB), anyInt(), any(), anyBoolean(), anyInt()); diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java index 80fb5e3f950d..1514de04fb08 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/KeySyncTaskTest.java @@ -89,6 +89,7 @@ public class KeySyncTaskTest { private static final String WRAPPING_KEY_ALIAS = "KeySyncTaskTest/WrappingKey"; private static final String DATABASE_FILE_NAME = "recoverablekeystore.db"; private static final int TEST_USER_ID = 1000; + private static final int TEST_USER_ID_2 = 1002; private static final int TEST_RECOVERY_AGENT_UID = 10009; private static final int TEST_RECOVERY_AGENT_UID2 = 10010; private static final byte[] TEST_VAULT_HANDLE = @@ -824,6 +825,48 @@ public class KeySyncTaskTest { } @Test + public void run_unlock_keepsRemoteLskfVerificationCounter() throws Exception { + mRecoverableKeyStoreDb.setBadRemoteGuessCounter(TEST_USER_ID, 5); + mRecoverableKeyStoreDb.setBadRemoteGuessCounter(TEST_USER_ID_2, 4); + mKeySyncTask = new KeySyncTask( + mRecoverableKeyStoreDb, + mRecoverySnapshotStorage, + mSnapshotListenersStorage, + TEST_USER_ID, + CREDENTIAL_TYPE_PIN, + "12345".getBytes(), + /*credentialUpdated=*/ false, + mPlatformKeyManager, + mTestOnlyInsecureCertificateHelper, + mMockScrypt); + mKeySyncTask.run(); + + assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(TEST_USER_ID)).isEqualTo(5); + assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(TEST_USER_ID_2)).isEqualTo(4); + } + + @Test + public void run_secretChange_resetsRemoteLskfVerificationCounter() throws Exception { + mRecoverableKeyStoreDb.setBadRemoteGuessCounter(TEST_USER_ID, 5); + mRecoverableKeyStoreDb.setBadRemoteGuessCounter(TEST_USER_ID_2, 4); + mKeySyncTask = new KeySyncTask( + mRecoverableKeyStoreDb, + mRecoverySnapshotStorage, + mSnapshotListenersStorage, + TEST_USER_ID, + CREDENTIAL_TYPE_PIN, + "12345".getBytes(), + /*credentialUpdated=*/ true, + mPlatformKeyManager, + mTestOnlyInsecureCertificateHelper, + mMockScrypt); + mKeySyncTask.run(); + + assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(TEST_USER_ID)).isEqualTo(0); + assertThat(mRecoverableKeyStoreDb.getBadRemoteGuessCounter(TEST_USER_ID_2)).isEqualTo(4); + } + + @Test public void run_customLockScreen_RecoveryStatusFailure() throws Exception { mKeySyncTask = new KeySyncTask( mRecoverableKeyStoreDb, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 5e2fe6a080eb..200952c05610 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -112,6 +112,7 @@ import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER; import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER; import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL; +import static com.android.server.notification.Flags.FLAG_REJECT_OLD_NOTIFICATIONS; import static com.android.server.notification.NotificationManagerService.BITMAP_DURATION; import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE; import static com.android.server.notification.NotificationManagerService.NOTIFICATION_TTL; @@ -339,6 +340,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -909,7 +911,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } mService.clearNotifications(); - TestableLooper.get(this).processAllMessages(); + if (mTestableLooper != null) { + mTestableLooper.processAllMessages(); + } try { mService.onDestroy(); @@ -920,14 +924,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { InstrumentationRegistry.getInstrumentation() .getUiAutomation().dropShellPermissionIdentity(); - // Remove scheduled messages that would be processed when the test is already done, and - // could cause issues, for example, messages that remove/cancel shown toasts (this causes - // problematic interactions with mocks when they're no longer working as expected). - mWorkerHandler.removeCallbacksAndMessages(null); + if (mWorkerHandler != null) { + // Remove scheduled messages that would be processed when the test is already done, and + // could cause issues, for example, messages that remove/cancel shown toasts (this causes + // problematic interactions with mocks when they're no longer working as expected). + mWorkerHandler.removeCallbacksAndMessages(null); + } - if (TestableLooper.get(this) != null) { + if (mTestableLooper != null) { // Must remove static reference to this test object to prevent leak (b/261039202) - TestableLooper.remove(this); + mTestableLooper.remove(this); } } @@ -1009,7 +1015,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } public void waitForIdle() { - mTestableLooper.processAllMessages(); + if (mTestableLooper != null) { + mTestableLooper.processAllMessages(); + } } private void setUpPrefsForBubbles(String pkg, int uid, boolean globalEnabled, @@ -1302,6 +1310,106 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { return nrSummary; } + private NotificationRecord createAndPostCallStyleNotification(String packageName, + UserHandle userHandle, String testName) throws Exception { + Person person = new Person.Builder().setName("caller").build(); + Notification.Builder nb = new Notification.Builder(mContext, + mTestNotificationChannel.getId()) + .setFlag(FLAG_USER_INITIATED_JOB, true) + .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent)) + .setSmallIcon(android.R.drawable.sym_def_app_icon); + StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, 1, + testName, mUid, 0, nb.build(), userHandle, null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mService.addEnqueuedNotification(r); + mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), + r.getUid(), mPostNotificationTrackerFactory.newTracker(null)).run(); + waitForIdle(); + + return mService.findNotificationLocked( + packageName, r.getSbn().getTag(), r.getSbn().getId(), r.getSbn().getUserId()); + } + + private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName) + throws RemoteException { + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1, testName, mUid, 0, + nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); + + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + + return mService.findNotificationLocked( + mPkg, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId()); + } + + private static <T extends Parcelable> T parcelAndUnparcel(T source, + Parcelable.Creator<T> creator) { + Parcel parcel = Parcel.obtain(); + source.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + return creator.createFromParcel(parcel); + } + + private PendingIntent createPendingIntent(String action) { + return PendingIntent.getActivity(mContext, 0, + new Intent(action).setPackage(mContext.getPackageName()), + PendingIntent.FLAG_MUTABLE); + } + + private void allowTestPackageToToast() throws Exception { + assertWithMessage("toast queue").that(mService.mToastQueue).isEmpty(); + mService.isSystemUid = false; + mService.isSystemAppId = false; + setToastRateIsWithinQuota(true); + setIfPackageHasPermissionToAvoidToastRateLimiting(TEST_PACKAGE, false); + // package is not suspended + when(mPackageManager.isPackageSuspendedForUser(TEST_PACKAGE, mUserId)) + .thenReturn(false); + } + + private boolean enqueueToast(String testPackage, ITransientNotification callback) + throws RemoteException { + return enqueueToast((INotificationManager) mService.mService, testPackage, new Binder(), + callback); + } + + private boolean enqueueToast(INotificationManager service, String testPackage, + IBinder token, ITransientNotification callback) throws RemoteException { + return service.enqueueToast(testPackage, token, callback, TOAST_DURATION, /* isUiContext= */ + true, DEFAULT_DISPLAY); + } + + private boolean enqueueTextToast(String testPackage, CharSequence text) throws RemoteException { + return enqueueTextToast(testPackage, text, /* isUiContext= */ true, DEFAULT_DISPLAY); + } + + private boolean enqueueTextToast(String testPackage, CharSequence text, boolean isUiContext, + int displayId) throws RemoteException { + return ((INotificationManager) mService.mService).enqueueTextToast(testPackage, + new Binder(), text, TOAST_DURATION, isUiContext, displayId, + /* textCallback= */ null); + } + + private void mockIsVisibleBackgroundUsersSupported(boolean supported) { + when(mUm.isVisibleBackgroundUsersSupported()).thenReturn(supported); + } + + private void mockIsUserVisible(int displayId, boolean visible) { + when(mUmInternal.isUserVisible(mUserId, displayId)).thenReturn(visible); + } + + private void mockDisplayAssignedToUser(int displayId) { + when(mUmInternal.getMainDisplayAssignedToUser(mUserId)).thenReturn(displayId); + } + + private void verifyToastShownForTestPackage(String text, int displayId) { + verify(mStatusBar).showToast(eq(mUid), eq(TEST_PACKAGE), any(), eq(text), any(), + eq(TOAST_DURATION), any(), eq(displayId)); + } + @Test @DisableFlags(FLAG_ALL_NOTIFS_NEED_TTL) public void testLimitTimeOutBroadcast() { @@ -5969,6 +6077,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(captor.getValue().getNotification().flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo( FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY); + assertThat(captor.getValue().getNotification().flags + & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE); assertThat(captor.getValue().shouldPostSilently()).isTrue(); } @@ -8798,6 +8908,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(captor.getValue().getNotification().flags & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo( FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY); + assertThat(captor.getValue().getNotification().flags + & FLAG_ONLY_ALERT_ONCE).isEqualTo(FLAG_ONLY_ALERT_ONCE); assertThat(captor.getValue().shouldPostSilently()).isTrue(); } @@ -14065,11 +14177,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void enqueueUpdate_whenBelowMaxEnqueueRate_accepts() throws Exception { // Post the first version. Notification original = generateNotificationRecord(null).getNotification(); - original.when = 111; + original.when = System.currentTimeMillis(); mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, original, mUserId); waitForIdle(); assertThat(mService.mNotificationList).hasSize(1); - assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); + assertThat(mService.mNotificationList.get(0).getNotification().when) + .isEqualTo(original.when); reset(mUsageStats); when(mUsageStats.getAppEnqueueRate(eq(mPkg))) @@ -14077,7 +14190,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Post the update. Notification update = generateNotificationRecord(null).getNotification(); - update.when = 222; + update.when = System.currentTimeMillis() + 111; mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, update, mUserId); waitForIdle(); @@ -14086,18 +14199,19 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mUsageStats, never()).registerPostedByApp(any()); verify(mUsageStats).registerUpdatedByApp(any(), any()); assertThat(mService.mNotificationList).hasSize(1); - assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(222); + assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(update.when); } @Test public void enqueueUpdate_whenAboveMaxEnqueueRate_rejects() throws Exception { // Post the first version. Notification original = generateNotificationRecord(null).getNotification(); - original.when = 111; + original.when = System.currentTimeMillis(); mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, original, mUserId); waitForIdle(); assertThat(mService.mNotificationList).hasSize(1); - assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); + assertThat(mService.mNotificationList.get(0).getNotification().when) + .isEqualTo(original.when); reset(mUsageStats); when(mUsageStats.getAppEnqueueRate(eq(mPkg))) @@ -14105,7 +14219,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Post the update. Notification update = generateNotificationRecord(null).getNotification(); - update.when = 222; + update.when = System.currentTimeMillis() + 111; mBinderService.enqueueNotificationWithTag(mPkg, mPkg, "tag", 0, update, mUserId); waitForIdle(); @@ -14114,7 +14228,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mUsageStats, never()).registerPostedByApp(any()); verify(mUsageStats, never()).registerUpdatedByApp(any(), any()); assertThat(mService.mNotificationList).hasSize(1); - assertThat(mService.mNotificationList.get(0).getNotification().when).isEqualTo(111); // old + assertThat(mService.mNotificationList.get(0).getNotification().when) + .isEqualTo(original.when); // old } @Test @@ -15479,103 +15594,48 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(n.getTimeoutAfter()).isEqualTo(20); } - private NotificationRecord createAndPostCallStyleNotification(String packageName, - UserHandle userHandle, String testName) throws Exception { - Person person = new Person.Builder().setName("caller").build(); - Notification.Builder nb = new Notification.Builder(mContext, - mTestNotificationChannel.getId()) - .setFlag(FLAG_USER_INITIATED_JOB, true) - .setStyle(Notification.CallStyle.forOngoingCall(person, mActivityIntent)) - .setSmallIcon(android.R.drawable.sym_def_app_icon); - StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, 1, - testName, mUid, 0, nb.build(), userHandle, null, 0); + @Test + @EnableFlags(FLAG_REJECT_OLD_NOTIFICATIONS) + public void testRejectOldNotification_oldWhen() throws Exception { + Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setWhen(System.currentTimeMillis() - Duration.ofDays(15).toMillis()) + .build(); + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - mService.addEnqueuedNotification(r); - mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), - r.getUid(), mPostNotificationTrackerFactory.newTracker(null)).run(); - waitForIdle(); - - return mService.findNotificationLocked( - packageName, r.getSbn().getTag(), r.getSbn().getId(), r.getSbn().getUserId()); - } - - private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName) - throws RemoteException { - StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 1, testName, mUid, 0, - nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); - NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - - mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(), - nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); - waitForIdle(); - - return mService.findNotificationLocked( - mPkg, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId()); - } - - private static <T extends Parcelable> T parcelAndUnparcel(T source, - Parcelable.Creator<T> creator) { - Parcel parcel = Parcel.obtain(); - source.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - return creator.createFromParcel(parcel); - } - - private PendingIntent createPendingIntent(String action) { - return PendingIntent.getActivity(mContext, 0, - new Intent(action).setPackage(mContext.getPackageName()), - PendingIntent.FLAG_MUTABLE); - } - - private void allowTestPackageToToast() throws Exception { - assertWithMessage("toast queue").that(mService.mToastQueue).isEmpty(); - mService.isSystemUid = false; - mService.isSystemAppId = false; - setToastRateIsWithinQuota(true); - setIfPackageHasPermissionToAvoidToastRateLimiting(TEST_PACKAGE, false); - // package is not suspended - when(mPackageManager.isPackageSuspendedForUser(TEST_PACKAGE, mUserId)) - .thenReturn(false); - } - - private boolean enqueueToast(String testPackage, ITransientNotification callback) - throws RemoteException { - return enqueueToast((INotificationManager) mService.mService, testPackage, new Binder(), - callback); - } - - private boolean enqueueToast(INotificationManager service, String testPackage, - IBinder token, ITransientNotification callback) throws RemoteException { - return service.enqueueToast(testPackage, token, callback, TOAST_DURATION, /* isUiContext= */ - true, DEFAULT_DISPLAY); - } - - private boolean enqueueTextToast(String testPackage, CharSequence text) throws RemoteException { - return enqueueTextToast(testPackage, text, /* isUiContext= */ true, DEFAULT_DISPLAY); - } - - private boolean enqueueTextToast(String testPackage, CharSequence text, boolean isUiContext, - int displayId) throws RemoteException { - return ((INotificationManager) mService.mService).enqueueTextToast(testPackage, - new Binder(), text, TOAST_DURATION, isUiContext, displayId, - /* textCallback= */ null); + assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false)) + .isFalse(); } - private void mockIsVisibleBackgroundUsersSupported(boolean supported) { - when(mUm.isVisibleBackgroundUsersSupported()).thenReturn(supported); - } + @Test + @EnableFlags(FLAG_REJECT_OLD_NOTIFICATIONS) + public void testRejectOldNotification_mediumOldWhen() throws Exception { + Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setWhen(System.currentTimeMillis() - Duration.ofDays(13).toMillis()) + .build(); + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - private void mockIsUserVisible(int displayId, boolean visible) { - when(mUmInternal.isUserVisible(mUserId, displayId)).thenReturn(visible); + assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false)) + .isTrue(); } - private void mockDisplayAssignedToUser(int displayId) { - when(mUmInternal.getMainDisplayAssignedToUser(mUserId)).thenReturn(displayId); - } + @Test + @EnableFlags(FLAG_REJECT_OLD_NOTIFICATIONS) + public void testRejectOldNotification_zeroWhen() throws Exception { + Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId()) + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setWhen(0) + .build(); + StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0, + n, UserHandle.getUserHandleForUid(mUid), null, 0); + NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - private void verifyToastShownForTestPackage(String text, int displayId) { - verify(mStatusBar).showToast(eq(mUid), eq(TEST_PACKAGE), any(), eq(text), any(), - eq(TOAST_DURATION), any(), eq(displayId)); + assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false)) + .isTrue(); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 44d1b5421b5b..87395a17698d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -113,15 +113,10 @@ import android.hardware.HardwareBuffer; import android.metrics.LogMaker; import android.os.Binder; import android.os.RemoteException; -import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.Presubmit; -import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.ArraySet; -import android.util.DisplayMetrics; import android.view.Display; import android.view.DisplayCutout; import android.view.DisplayInfo; @@ -131,7 +126,6 @@ import android.view.IDisplayChangeWindowController; import android.view.ISystemGestureExclusionListener; import android.view.IWindowManager; import android.view.InsetsState; -import android.view.MotionEvent; import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; @@ -151,7 +145,6 @@ import com.android.server.LocalServices; import com.android.server.policy.WindowManagerPolicy; import com.android.server.wm.utils.WmDisplayCutout; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -178,10 +171,6 @@ import java.util.concurrent.TimeoutException; @RunWith(WindowTestRunner.class) public class DisplayContentTests extends WindowTestsBase { - @Rule - public final CheckFlagsRule mCheckFlagsRule = - DeviceFlagsValueProvider.createCheckFlagsRule(); - @SetupWindows(addAllCommonWindows = true) @Test public void testForAllWindows() { @@ -514,44 +503,6 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(currentConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */); } - /** - * Tests tapping on a root task in different display results in window gaining focus. - */ - @Test - @RequiresFlagsDisabled(com.android.input.flags.Flags.FLAG_REMOVE_POINTER_EVENT_TRACKING_IN_WM) - public void testInputEventBringsCorrectDisplayInFocus() { - DisplayContent dc0 = mWm.getDefaultDisplayContentLocked(); - // Create a second display - final DisplayContent dc1 = createNewDisplay(); - - // Add root task with activity. - final Task rootTask0 = createTask(dc0); - final Task task0 = createTaskInRootTask(rootTask0, 0 /* userId */); - final ActivityRecord activity = createNonAttachedActivityRecord(dc0); - task0.addChild(activity, 0); - dc0.configureDisplayPolicy(); - assertNotNull(dc0.mTapDetector); - - final Task rootTask1 = createTask(dc1); - final Task task1 = createTaskInRootTask(rootTask1, 0 /* userId */); - final ActivityRecord activity1 = createNonAttachedActivityRecord(dc0); - task1.addChild(activity1, 0); - dc1.configureDisplayPolicy(); - assertNotNull(dc1.mTapDetector); - - // tap on primary display. - tapOnDisplay(dc0); - // Check focus is on primary display. - assertEquals(mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus, - dc0.findFocusedWindow()); - - // Tap on secondary display. - tapOnDisplay(dc1); - // Check focus is on secondary. - assertEquals(mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus, - dc1.findFocusedWindow()); - } - @Test public void testFocusedWindowMultipleDisplays() { doTestFocusedWindowMultipleDisplays(false /* perDisplayFocusEnabled */, Q); @@ -2959,33 +2910,4 @@ public class DisplayContentTests extends WindowTestsBase { throw new RuntimeException(e); } } - - private void tapOnDisplay(final DisplayContent dc) { - final DisplayMetrics dm = dc.getDisplayMetrics(); - final float x = dm.widthPixels / 2; - final float y = dm.heightPixels / 2; - final long downTime = SystemClock.uptimeMillis(); - final long eventTime = SystemClock.uptimeMillis() + 100; - // sending ACTION_DOWN - final MotionEvent downEvent = MotionEvent.obtain( - downTime, - downTime, - MotionEvent.ACTION_DOWN, - x, - y, - 0 /*metaState*/); - downEvent.setDisplayId(dc.getDisplayId()); - dc.mTapDetector.onPointerEvent(downEvent); - - // sending ACTION_UP - final MotionEvent upEvent = MotionEvent.obtain( - downTime, - eventTime, - MotionEvent.ACTION_UP, - x, - y, - 0 /*metaState*/); - upEvent.setDisplayId(dc.getDisplayId()); - dc.mTapDetector.onPointerEvent(upEvent); - } } diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java index b90fa21cb2b1..79e401c238bd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java @@ -18,15 +18,17 @@ package com.android.server.wm; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER; import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP; - import static com.android.server.wm.testing.Assert.assertThrows; +import static junit.framework.Assert.assertEquals; + import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -35,11 +37,13 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.content.res.Resources; import android.platform.test.annotations.Presubmit; +import android.util.DisplayMetrics; import androidx.test.filters.SmallTest; -import com.android.server.wm.testing.Assert; +import com.android.internal.R; import org.junit.Before; import org.junit.Test; @@ -277,7 +281,7 @@ public class LetterboxConfigurationTest { } @Test - public void test_lettterboxPositionWhenReachabilityEnabledIsSet() { + public void test_letterboxPositionWhenReachabilityEnabledIsSet() { // Check that horizontal reachability is set with correct arguments mLetterboxConfiguration.setPersistentLetterboxPositionForHorizontalReachability( false /* forBookMode */, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT); @@ -344,4 +348,48 @@ public class LetterboxConfigurationTest { mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(0.5f); mLetterboxConfiguration.setLetterboxTabletopModePositionMultiplier(1); } + + @Test + public void test_evaluateThinLetterboxWhenDensityChanges() { + final Resources rs = mock(Resources.class); + final DisplayMetrics dm = mock(DisplayMetrics.class); + final LetterboxConfigurationPersister lp = mock(LetterboxConfigurationPersister.class); + spyOn(mContext); + when(rs.getDisplayMetrics()).thenReturn(dm); + when(mContext.getResources()).thenReturn(rs); + when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxWidthDp)) + .thenReturn(100); + when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxHeightDp)) + .thenReturn(200); + final LetterboxConfiguration configuration = new LetterboxConfiguration(mContext, lp); + + // Verify the values are the expected ones + dm.density = 100; + when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxWidthDp)) + .thenReturn(100); + when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxHeightDp)) + .thenReturn(200); + final int thinWidthPx = configuration.getThinLetterboxWidthPx(); + final int thinHeightPx = configuration.getThinLetterboxHeightPx(); + assertEquals(100, thinWidthPx); + assertEquals(200, thinHeightPx); + + // We change the values in the resources but not the update condition (density) and the + // result should not change + when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxWidthDp)) + .thenReturn(300); + when(rs.getDimensionPixelSize(R.dimen.config_letterboxThinLetterboxHeightDp)) + .thenReturn(400); + final int thinWidthPx2 = configuration.getThinLetterboxWidthPx(); + final int thinHeightPx2 = configuration.getThinLetterboxHeightPx(); + assertEquals(100, thinWidthPx2); + assertEquals(200, thinHeightPx2); + + // We update the condition (density) so the new resource values should be read + dm.density = 150; + final int thinWidthPx3 = configuration.getThinLetterboxWidthPx(); + final int thinHeightPx3 = configuration.getThinLetterboxHeightPx(); + assertEquals(300, thinWidthPx3); + assertEquals(400, thinHeightPx3); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index a60d243c9dad..1195c934a6f7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -1623,6 +1623,12 @@ public class LetterboxUiControllerTest extends WindowTestsBase { assertTrue(mController.allowHorizontalReachabilityForThinLetterbox()); } + @Test + public void testIsLetterboxEducationEnabled() { + mController.isLetterboxEducationEnabled(); + verify(mLetterboxConfiguration).getIsEducationEnabled(); + } + private void mockThatProperty(String propertyName, boolean value) throws Exception { Property property = new Property(propertyName, /* value */ value, /* packageName */ "", /* className */ ""); 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 afa669807c2e..d9fd312423d1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -1894,11 +1894,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { public void testApplyTransaction_createTaskFragmentDecorSurface() { mSetFlagsRule.enableFlags(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG); - // TODO(b/293654166) remove system organizer requirement once security review is cleared. - mController.unregisterOrganizer(mIOrganizer); - registerTaskFragmentOrganizer(mIOrganizer, true /* isSystemOrganizer */); final Task task = createTask(mDisplayContent); - final TaskFragment tf = createTaskFragment(task); final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE).build(); @@ -1913,9 +1909,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { public void testApplyTransaction_removeTaskFragmentDecorSurface() { mSetFlagsRule.enableFlags(Flags.FLAG_TASK_FRAGMENT_SYSTEM_ORGANIZER_FLAG); - // TODO(b/293654166) remove system organizer requirement once security review is cleared. - mController.unregisterOrganizer(mIOrganizer); - registerTaskFragmentOrganizer(mIOrganizer, true /* isSystemOrganizer */); final Task task = createTask(mDisplayContent); final TaskFragment tf = createTaskFragment(task); diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java index 37de51eccff2..4fc222b3e038 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java @@ -95,10 +95,6 @@ public class TestIWindow extends IWindow.Stub { } @Override - public void updatePointerIcon(float x, float y) throws RemoteException { - } - - @Override public void dispatchWindowShown() throws RemoteException { } @@ -128,4 +124,9 @@ public class TestIWindow extends IWindow.Stub { public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) throws RemoteException { } + + @Override + public void dumpWindow(ParcelFileDescriptor pfd) { + + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index fbbb9a2e55a7..b152c3e5355f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -819,17 +819,20 @@ public class WindowStateTests extends WindowTestsBase { assertFalse(win.getOrientationChanging()); } - @SetupWindows(addWindows = W_ABOVE_ACTIVITY) @Test public void testRequestResizeForBlastSync() { - final WindowState win = mChildAppWindowAbove; - makeWindowVisible(win, win.getParentWindow()); + final WindowState win = createWindow(null, TYPE_APPLICATION, "window"); + makeWindowVisible(win); + makeLastConfigReportedToClient(win, true /* visible */); win.mLayoutSeq = win.getDisplayContent().mLayoutSeq; win.reportResized(); win.updateResizingWindowIfNeeded(); assertThat(mWm.mResizingWindows).doesNotContain(win); // Check that the window is in resizing if using blast sync. + final BLASTSyncEngine.SyncGroup syncGroup = mock(BLASTSyncEngine.SyncGroup.class); + syncGroup.mSyncMethod = BLASTSyncEngine.METHOD_BLAST; + win.mSyncGroup = syncGroup; win.reportResized(); win.prepareSync(); assertEquals(SYNC_STATE_WAITING_FOR_DRAW, win.mSyncState); @@ -842,6 +845,20 @@ public class WindowStateTests extends WindowTestsBase { mWm.mResizingWindows.remove(win); win.updateResizingWindowIfNeeded(); assertThat(mWm.mResizingWindows).doesNotContain(win); + + // Non blast sync doesn't require to force resizing, because it won't use syncSeqId. + // And if the window is already drawn, it can report sync finish immediately so that the + // sync group won't be blocked. + win.finishSync(mTransaction, syncGroup, false /* cancel */); + syncGroup.mSyncMethod = BLASTSyncEngine.METHOD_NONE; + win.mSyncGroup = syncGroup; + win.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN; + win.prepareSync(); + assertEquals(SYNC_STATE_WAITING_FOR_DRAW, win.mSyncState); + win.updateResizingWindowIfNeeded(); + assertThat(mWm.mResizingWindows).doesNotContain(win); + assertTrue(win.isSyncFinished(syncGroup)); + assertEquals(WindowContainer.SYNC_STATE_READY, win.mSyncState); } @Test diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 03ba8faa64de..dbe4f2782616 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -13114,39 +13114,41 @@ public class TelephonyManager { }) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS) public @Nullable ServiceState getServiceState(@IncludeLocationData int includeLocationData) { - return getServiceStateForSubscriber(getSubId(), + return getServiceStateForSlot(SubscriptionManager.getSlotIndex(getSubId()), includeLocationData != INCLUDE_LOCATION_DATA_FINE, includeLocationData == INCLUDE_LOCATION_DATA_NONE); } /** - * Returns the service state information on specified subscription. Callers require - * either READ_PRIVILEGED_PHONE_STATE or READ_PHONE_STATE to retrieve the information. + * Returns the service state information on specified SIM slot. * - * May return {@code null} when the subscription is inactive or when there was an error + * May return {@code null} when the {@code slotIndex} is invalid or when there was an error * communicating with the phone process. + * + * @param slotIndex of phone whose service state is returned * @param renounceFineLocationAccess Set this to true if the caller would not like to receive * location related information which will be sent if the caller already possess * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and do not renounce the permission * @param renounceCoarseLocationAccess Set this to true if the caller would not like to * receive location related information which will be sent if the caller already possess * {@link Manifest.permission#ACCESS_COARSE_LOCATION} and do not renounce the permissions. + * @return Service state on specified SIM slot. */ - private ServiceState getServiceStateForSubscriber(int subId, - boolean renounceFineLocationAccess, + private ServiceState getServiceStateForSlot(int slotIndex, boolean renounceFineLocationAccess, boolean renounceCoarseLocationAccess) { try { ITelephony service = getITelephony(); if (service != null) { - return service.getServiceStateForSubscriber(subId, renounceFineLocationAccess, - renounceCoarseLocationAccess, getOpPackageName(), getAttributionTag()); + return service.getServiceStateForSlot(slotIndex, + renounceFineLocationAccess, renounceCoarseLocationAccess, + getOpPackageName(), getAttributionTag()); } } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelephony#getServiceStateForSubscriber", e); + Log.e(TAG, "Error calling ITelephony#getServiceStateForSlot", e); } catch (NullPointerException e) { AnomalyReporter.reportAnomaly( UUID.fromString("e2bed88e-def9-476e-bd71-3e572a8de6d1"), - "getServiceStateForSubscriber " + subId + " NPE"); + "getServiceStateForSlot " + slotIndex + " NPE"); } return null; } @@ -13161,7 +13163,35 @@ public class TelephonyManager { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public ServiceState getServiceStateForSubscriber(int subId) { - return getServiceStateForSubscriber(subId, false, false); + return getServiceStateForSlot( + SubscriptionManager.getSlotIndex(subId), false, false); + } + + /** + * Returns the service state information on specified SIM slot. + * + * If you want continuous updates of service state info, register a {@link TelephonyCallback} + * that implements {@link TelephonyCallback.ServiceStateListener} through + * {@link #registerTelephonyCallback}. + * + * May return {@code null} when the {@code slotIndex} is invalid or when there was an error + * communicating with the phone process + * + * See {@link #getActiveModemCount()} to get the total number of slots + * that are active on the device. + * + * @param slotIndex of phone whose service state is returned + * @return ServiceState on specified SIM slot. + * + * @hide + */ + @RequiresPermission(allOf = { + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_COARSE_LOCATION + }) + @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS) + public @Nullable ServiceState getServiceStateForSlot(int slotIndex) { + return getServiceStateForSlot(slotIndex, false, false); } /** diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 0bb5fd513fd1..47f53f372d33 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -1015,13 +1015,27 @@ public final class SatelliteManager { * @hide */ public static final int DATAGRAM_TYPE_KEEP_ALIVE = 3; + /** + * Datagram type indicating that the datagram to be sent or received is of type SOS message and + * is the last message to emergency service provider indicating still needs help. + * @hide + */ + public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP = 4; + /** + * Datagram type indicating that the datagram to be sent or received is of type SOS message and + * is the last message to emergency service provider indicating no more help is needed. + * @hide + */ + public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED = 5; /** @hide */ @IntDef(prefix = "DATAGRAM_TYPE_", value = { DATAGRAM_TYPE_UNKNOWN, DATAGRAM_TYPE_SOS_MESSAGE, DATAGRAM_TYPE_LOCATION_SHARING, - DATAGRAM_TYPE_KEEP_ALIVE + DATAGRAM_TYPE_KEEP_ALIVE, + DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP, + DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED }) @Retention(RetentionPolicy.SOURCE) public @interface DatagramType {} diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index d4da7364b07d..65de7e479890 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1399,19 +1399,18 @@ interface ITelephony { oneway void requestModemActivityInfo(in ResultReceiver result); /** - * Get the service state on specified subscription - * @param subId Subscription id + * Get the service state on specified SIM slot. + * @param slotIndex of phone whose service state is returned * @param renounceFineLocationAccess Set this to true if the caller would not like to * receive fine location related information * @param renounceCoarseLocationAccess Set this to true if the caller would not like to * receive coarse location related information * @param callingPackage The package making the call * @param callingFeatureId The feature in the package - * @return Service state on specified subscription. + * @return Service state on specified SIM slot. */ - ServiceState getServiceStateForSubscriber(int subId, boolean renounceFineLocationAccess, - boolean renounceCoarseLocationAccess, - String callingPackage, String callingFeatureId); + ServiceState getServiceStateForSlot(int slotIndex, boolean renounceFineLocationAccess, + boolean renounceCoarseLocationAccess, String callingPackage, String callingFeatureId); /** * Returns the URI for the per-account voicemail ringtone set in Phone settings. diff --git a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt index dfbbda6c6f5e..afb3593e3e98 100644 --- a/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt +++ b/tests/AttestationVerificationTest/src/com/android/server/security/AttestationVerificationPeerDeviceVerifierTest.kt @@ -9,21 +9,28 @@ import android.security.attestationverification.AttestationVerificationManager.R import android.security.attestationverification.AttestationVerificationManager.RESULT_SUCCESS import android.security.attestationverification.AttestationVerificationManager.TYPE_CHALLENGE import android.security.attestationverification.AttestationVerificationManager.TYPE_PUBLIC_KEY +import android.util.IndentingPrintWriter +import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.security.AttestationVerificationManagerService.DumpLogger import com.google.common.truth.Truth.assertThat -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.MockitoAnnotations import java.io.ByteArrayOutputStream +import java.io.PrintWriter +import java.io.StringWriter import java.security.cert.Certificate import java.security.cert.CertificateFactory import java.security.cert.TrustAnchor import java.security.cert.X509Certificate import java.time.LocalDate +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + /** Test for Peer Device attestation verifier. */ @SmallTest @@ -31,6 +38,7 @@ import java.time.LocalDate class AttestationVerificationPeerDeviceVerifierTest { private val certificateFactory = CertificateFactory.getInstance("X.509") @Mock private lateinit var context: Context + private val dumpLogger = DumpLogger() private lateinit var trustAnchors: HashSet<TrustAnchor> @Before @@ -44,37 +52,50 @@ class AttestationVerificationPeerDeviceVerifierTest { } } + @After + fun dumpAndLog() { + val dump = dumpLogger.getDump() + Log.d(TAG, "$dump") + } + @Test fun verifyAttestation_returnsSuccessTypeChallenge() { val verifier = AttestationVerificationPeerDeviceVerifier( - context, trustAnchors, false, LocalDate.of(2022, 2, 1), - LocalDate.of(2021, 8, 1)) + context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1), + LocalDate.of(2021, 8, 1) + ) val challengeRequirements = Bundle() challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) - val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, - TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + val result = verifier.verifyAttestation( + TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() + ) assertThat(result).isEqualTo(RESULT_SUCCESS) } @Test fun verifyAttestation_returnsSuccessLocalPatchOlderThanOneYear() { val verifier = AttestationVerificationPeerDeviceVerifier( - context, trustAnchors, false, LocalDate.of(2022, 2, 1), - LocalDate.of(2021, 1, 1)) + context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1), + LocalDate.of(2021, 1, 1) + ) val challengeRequirements = Bundle() challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) - val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, - TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + val result = verifier.verifyAttestation( + TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() + ) assertThat(result).isEqualTo(RESULT_SUCCESS) } @Test fun verifyAttestation_returnsSuccessTypePublicKey() { val verifier = AttestationVerificationPeerDeviceVerifier( - context, trustAnchors, false, LocalDate.of(2022, 2, 1), - LocalDate.of(2021, 8, 1)) + context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1), + LocalDate.of(2021, 8, 1) + ) val leafCert = (TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToCerts() as List)[0] @@ -84,61 +105,75 @@ class AttestationVerificationPeerDeviceVerifierTest { val result = verifier.verifyAttestation( TYPE_PUBLIC_KEY, pkRequirements, - TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() + ) assertThat(result).isEqualTo(RESULT_SUCCESS) } @Test fun verifyAttestation_returnsSuccessOwnedBySystem() { val verifier = AttestationVerificationPeerDeviceVerifier( - context, trustAnchors, false, LocalDate.of(2022, 2, 1), - LocalDate.of(2021, 1, 1)) + context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1), + LocalDate.of(2021, 1, 1) + ) val challengeRequirements = Bundle() challengeRequirements.putByteArray(PARAM_CHALLENGE, "activeUnlockValid".encodeToByteArray()) challengeRequirements.putBoolean("android.key_owned_by_system", true) - val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, - TEST_OWNED_BY_SYSTEM_FILENAME.fromPEMFileToByteArray()) + val result = verifier.verifyAttestation( + TYPE_CHALLENGE, challengeRequirements, + TEST_OWNED_BY_SYSTEM_FILENAME.fromPEMFileToByteArray() + ) + assertThat(result).isEqualTo(RESULT_SUCCESS) } @Test fun verifyAttestation_returnsFailureOwnedBySystem() { val verifier = AttestationVerificationPeerDeviceVerifier( - context, trustAnchors, false, LocalDate.of(2022, 2, 1), - LocalDate.of(2021, 1, 1)) + context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 2, 1), + LocalDate.of(2021, 1, 1) + ) val challengeRequirements = Bundle() challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) challengeRequirements.putBoolean("android.key_owned_by_system", true) - val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, - TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + val result = verifier.verifyAttestation( + TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() + ) assertThat(result).isEqualTo(RESULT_FAILURE) } @Test fun verifyAttestation_returnsFailurePatchDateNotWithinOneYearLocalPatch() { val verifier = AttestationVerificationPeerDeviceVerifier( - context, trustAnchors, false, LocalDate.of(2023, 3, 1), - LocalDate.of(2023, 2, 1)) + context, dumpLogger, trustAnchors, false, LocalDate.of(2023, 3, 1), + LocalDate.of(2023, 2, 1) + ) val challengeRequirements = Bundle() challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) - val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, - TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + val result = verifier.verifyAttestation( + TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() + ) assertThat(result).isEqualTo(RESULT_FAILURE) } @Test fun verifyAttestation_returnsFailureTrustedAnchorEmpty() { val verifier = AttestationVerificationPeerDeviceVerifier( - context, HashSet(), false, LocalDate.of(2022, 1, 1), - LocalDate.of(2022, 1, 1)) + context, dumpLogger, HashSet(), false, LocalDate.of(2022, 1, 1), + LocalDate.of(2022, 1, 1) + ) val challengeRequirements = Bundle() challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) - val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, - TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + val result = verifier.verifyAttestation( + TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() + ) assertThat(result).isEqualTo(RESULT_FAILURE) } @@ -151,32 +186,39 @@ class AttestationVerificationPeerDeviceVerifierTest { } val verifier = AttestationVerificationPeerDeviceVerifier( - context, badTrustAnchors, false, LocalDate.of(2022, 1, 1), - LocalDate.of(2022, 1, 1)) + context, dumpLogger, badTrustAnchors, false, LocalDate.of(2022, 1, 1), + LocalDate.of(2022, 1, 1) + ) val challengeRequirements = Bundle() challengeRequirements.putByteArray(PARAM_CHALLENGE, "player456".encodeToByteArray()) - val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, - TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + val result = verifier.verifyAttestation( + TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() + ) assertThat(result).isEqualTo(RESULT_FAILURE) } fun verifyAttestation_returnsFailureChallenge() { val verifier = AttestationVerificationPeerDeviceVerifier( - context, trustAnchors, false, LocalDate.of(2022, 1, 1), - LocalDate.of(2022, 1, 1)) + context, dumpLogger, trustAnchors, false, LocalDate.of(2022, 1, 1), + LocalDate.of(2022, 1, 1) + ) val challengeRequirements = Bundle() challengeRequirements.putByteArray(PARAM_CHALLENGE, "wrong".encodeToByteArray()) - val result = verifier.verifyAttestation(TYPE_CHALLENGE, challengeRequirements, - TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray()) + val result = verifier.verifyAttestation( + TYPE_CHALLENGE, challengeRequirements, + TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME.fromPEMFileToByteArray() + ) assertThat(result).isEqualTo(RESULT_FAILURE) } private fun String.fromPEMFileToCerts(): Collection<Certificate> { return certificateFactory.generateCertificates( InstrumentationRegistry.getInstrumentation().getContext().getResources().getAssets() - .open(this)) + .open(this) + ) } private fun String.fromPEMFileToByteArray(): ByteArray { @@ -188,6 +230,12 @@ class AttestationVerificationPeerDeviceVerifierTest { return bos.toByteArray() } + private fun DumpLogger.getDump(): String { + val sw = StringWriter() + this.dumpTo(IndentingPrintWriter(PrintWriter(sw), " ")) + return sw.toString() + } + class TestActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -195,6 +243,7 @@ class AttestationVerificationPeerDeviceVerifierTest { } companion object { + private const val TAG = "AVFTest" private const val TEST_ROOT_CERT_FILENAME = "test_root_certs.pem" private const val TEST_ATTESTATION_WITH_ROOT_CERT_FILENAME = "test_attestation_with_root_certs.pem" diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt index 3b9ee807077e..3c72498082e4 100644 --- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt +++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt @@ -169,12 +169,6 @@ class InputManagerServiceTests { localService.setDisplayViewports(viewports) verify(native).setDisplayViewports(any(Array<DisplayViewport>::class.java)) verify(native).setPointerDisplayId(displayId) - - val x = 42f - val y = 314f - service.onPointerDisplayIdChanged(displayId, x, y) - testLooper.dispatchNext() - verify(wmCallbacks).notifyPointerDisplayIdChanged(displayId, x, y) } @Test @@ -317,9 +311,8 @@ class InputManagerServiceTests { verify(mockOnKeyListener, never()).onKey(mockSurfaceView1, KeyEvent.KEYCODE_A, upEvent) } - // TODO(b/324075859): Rename this method to addUniqueIdAssociationByPort_verifyAssociations @Test - fun addUniqueIdAssociation_verifyAssociations() { + fun addUniqueIdAssociationByPort_verifyAssociations() { // Overall goal is to have 2 displays and verify that events from the InputDevice are // sent only to the view that is on the associated display. // So, associate the InputDevice with display 1, then send and verify KeyEvents. @@ -340,7 +333,7 @@ class InputManagerServiceTests { val inputDevice = createInputDevice() // Associate input device with display - service.addUniqueIdAssociation( + service.addUniqueIdAssociationByPort( inputDevice.name, virtualDisplays[0].display.displayId.toString() ) @@ -364,10 +357,10 @@ class InputManagerServiceTests { verify(mockOnKeyListener, never()).onKey(mockSurfaceView2, KeyEvent.KEYCODE_A, downEvent) // Remove association - service.removeUniqueIdAssociation(inputDevice.name) + service.removeUniqueIdAssociationByPort(inputDevice.name) // Associate with Display 2 - service.addUniqueIdAssociation( + service.addUniqueIdAssociationByPort( inputDevice.name, virtualDisplays[1].display.displayId.toString() ) diff --git a/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java b/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java index 3ab8d370750f..6bcfebc752f1 100644 --- a/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java +++ b/tests/StatusBar/src/com/android/statusbartest/StatusBarTest.java @@ -176,25 +176,19 @@ public class StatusBarTest extends TestActivity }, new Test("Disable Alerts") { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setNotificationPeekingDisabled(true); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(StatusBarManager.DISABLE_NOTIFICATION_ALERTS); } }, new Test("Disable Ticker") { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setNotificationTickerDisabled(true); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(StatusBarManager.DISABLE_NOTIFICATION_TICKER); } }, new Test("Disable Expand in 3 sec.") { public void run() { mHandler.postDelayed(new Runnable() { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setStatusBarExpansionDisabled(true); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND); } }, 3000); } @@ -203,9 +197,7 @@ public class StatusBarTest extends TestActivity public void run() { mHandler.postDelayed(new Runnable() { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setNotificationIconsDisabled(true); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(StatusBarManager.DISABLE_NOTIFICATION_ICONS); } }, 3000); } @@ -214,73 +206,56 @@ public class StatusBarTest extends TestActivity public void run() { mHandler.postDelayed(new Runnable() { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setStatusBarExpansionDisabled(true); - info.setNotificationIconsDisabled(true); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND + | StatusBarManager.DISABLE_NOTIFICATION_ICONS); } }, 3000); } }, new Test("Disable Home (StatusBarManager)") { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setNavigationHomeDisabled(true); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(StatusBarManager.DISABLE_HOME); } }, new Test("Disable Back (StatusBarManager)") { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setBackDisabled(true); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(StatusBarManager.DISABLE_BACK); } }, new Test("Disable Recent (StatusBarManager)") { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setRecentsDisabled(true); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(StatusBarManager.DISABLE_RECENT); } }, new Test("Disable Clock") { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setClockDisabled(true); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(StatusBarManager.DISABLE_CLOCK); } }, new Test("Disable System Info") { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setSystemIconsDisabled(true); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(StatusBarManager.DISABLE_SYSTEM_INFO); } }, new Test("Disable everything in 3 sec") { public void run() { mHandler.postDelayed(new Runnable() { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setDisableAll(); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(~StatusBarManager.DISABLE_NONE); } }, 3000); } }, new Test("Enable everything") { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(StatusBarManager.DISABLE_NONE); } }, new Test("Enable everything in 3 sec.") { public void run() { mHandler.postDelayed(new Runnable() { public void run() { - StatusBarManager.DisableInfo info = new StatusBarManager.DisableInfo(); - info.setEnableAll(); - mStatusBarManager.requestDisabledComponent(info, "test"); + mStatusBarManager.disable(0); } }, 3000); } diff --git a/tests/graphics/SilkFX/AndroidManifest.xml b/tests/graphics/SilkFX/AndroidManifest.xml index c293589bdbaf..25092b52e2b6 100644 --- a/tests/graphics/SilkFX/AndroidManifest.xml +++ b/tests/graphics/SilkFX/AndroidManifest.xml @@ -23,12 +23,13 @@ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <application android:label="SilkFX" - android:theme="@android:style/Theme.Material"> + android:theme="@style/Theme.UsefulDefault"> <activity android:name=".Main" android:label="SilkFX Demos" android:banner="@drawable/background1" - android:exported="true"> + android:exported="true" + android:theme="@style/Theme.UsefulDefault"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.DEFAULT"/> diff --git a/tests/graphics/SilkFX/res/values/style.xml b/tests/graphics/SilkFX/res/values/style.xml index 66edbb5c9382..4dd626dfb8f5 100644 --- a/tests/graphics/SilkFX/res/values/style.xml +++ b/tests/graphics/SilkFX/res/values/style.xml @@ -23,9 +23,14 @@ <item name="android:windowElevation">0dp</item> <item name="buttonStyle">@style/AppTheme.Button</item> <item name="colorAccent">#bbffffff</item> + <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> </style> <style name="AppTheme.Button" parent="Widget.AppCompat.Button"> <item name="android:textColor">#ffffffff</item> </style> + <style name="Theme.UsefulDefault" parent="android:Theme.Material"> + <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> + </style> + </resources> diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java index e3f84c19653b..f1260008ca59 100644 --- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java +++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/MainActivity.java @@ -24,6 +24,7 @@ import static com.android.server.inputmethod.multisessiontest.TestRequestConstan import android.app.Activity; import android.os.Bundle; +import android.os.Process; import android.util.Log; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; @@ -47,8 +48,9 @@ public final class MainActivity extends ConcurrentUserActivityBase { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Log.v(TAG, "Create MainActivity as user " + getUserId() + " on display " - + getDisplayId()); + Log.v(TAG, "Create MainActivity as user " + + Process.myUserHandle().getIdentifier() + " on display " + + getDisplay().getDisplayId()); setContentView(R.layout.main_activity); mImm = getSystemService(InputMethodManager.class); mEditor = requireViewById(R.id.edit_text); diff --git a/tools/hoststubgen/TEST_MAPPING b/tools/hoststubgen/TEST_MAPPING index f6885e1e74ba..856e6eefba15 100644 --- a/tools/hoststubgen/TEST_MAPPING +++ b/tools/hoststubgen/TEST_MAPPING @@ -1,63 +1,7 @@ -// Keep the following two TEST_MAPPINGs in sync: -// frameworks/base/ravenwood/TEST_MAPPING -// frameworks/base/tools/hoststubgen/TEST_MAPPING { - "presubmit": [ - { "name": "tiny-framework-dump-test" }, - { "name": "hoststubgentest" }, - { "name": "hoststubgen-invoke-test" }, + "imports": [ { - "name": "RavenwoodMockitoTest_device" - }, - { - "name": "RavenwoodBivalentTest_device" - }, - // The sysui tests should match vendor/unbundled_google/packages/SystemUIGoogle/TEST_MAPPING - { - "name": "SystemUIGoogleTests", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] - } - ], - "presubmit-large": [ - { - "name": "SystemUITests", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] - } - ], - "ravenwood-presubmit": [ - { - "name": "RavenwoodMinimumTest", - "host": true - }, - { - "name": "RavenwoodMockitoTest", - "host": true - }, - { - "name": "CtsUtilTestCasesRavenwood", - "host": true - }, - { - "name": "RavenwoodCoreTest", - "host": true - }, - { - "name": "RavenwoodBivalentTest", - "host": true + "path": "frameworks/base/ravenwood" } ] } diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java index 3d5a0f7a239f..395f744ef043 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java @@ -21,6 +21,7 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.net.wifi.flags.Flags; import android.net.wifi.sharedconnectivity.service.SharedConnectivityService; import android.os.Bundle; import android.os.Parcel; @@ -170,7 +171,7 @@ public final class NetworkProviderInfo implements Parcelable { * @return Returns the Builder object. */ @NonNull - @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") + @FlaggedApi(Flags.FLAG_NETWORK_PROVIDER_BATTERY_CHARGING_STATUS) public Builder setBatteryCharging(boolean isBatteryCharging) { mIsBatteryCharging = isBatteryCharging; return this; @@ -285,7 +286,7 @@ public final class NetworkProviderInfo implements Parcelable { * * @return Returns true if the battery of the remote device is charging. */ - @FlaggedApi("com.android.wifi.flags.network_provider_battery_charging_status") + @FlaggedApi(Flags.FLAG_NETWORK_PROVIDER_BATTERY_CHARGING_STATUS) public boolean isBatteryCharging() { return mIsBatteryCharging; } |